From 21cb9d2d933e2755b9e33ef9b89d06e92d8cc1a0 Mon Sep 17 00:00:00 2001 From: James Carthew Date: Mon, 1 Apr 2024 22:10:51 +1030 Subject: [PATCH 01/26] Updated to meson build system. Removed Autotools. --- .travis.yml | 69 -- .travis/key | Bin 1680 -> 0 bytes .travis/obs.sh | 57 -- .travis/win32-build.sh | 13 - .travis/win32-deploy.sh | 11 - .travis/win32-install.sh | 24 - AUTHORS | 3 +- ChangeLog | 5 + MANIFEST_PIDGIN | 25 - MANIFEST_VOIDS | 12 - Makefile.am | 17 - Makefile.mingw | 66 -- README | 4 + RELEASE_VERSION | 1 - VERSION | 1 - autogen.sh | 15 - configure.ac | 112 --- debian/control | 2 +- debian/rules | 6 +- dist/purple-facebook.spec | 4 +- meson.build | 93 +++ meson_options.txt | 10 + patches/01-makefile.patch | 68 -- patches/02-glibcompat.patch | 33 - patches/03-plugin.patch | 398 ----------- patches/04-revert-gio.patch | 530 --------------- patches/05-revert-http-callbacks.patch | 42 -- patches/06-revert-purple-socket.patch | 637 ------------------ patches/07-revert-http-gio.patch | 428 ------------ patches/08-revert-marshaller.patch | 367 ---------- patches/09-revert-http-zlib.patch | 148 ---- patches/10-glib-compiled-with-debug.patch | 43 -- patches/11-contacts.patch | 11 - ...b-error-cast-bug-2-electric-boogaloo.patch | 14 - patches/13-async-sockets-are-hard.patch | 51 -- patches/14-the-future.patch | 11 - ...b-deprecate-g_type_class_add_private.patch | 99 --- patches/16-fix-duplicate-decl-specifier.patch | 13 - patches/17-this-build-system-sucks.patch | 12 - patches/18-fix-thrift-stop-failure.patch | 11 - patches/19-fix-taNewMessage-bug.patch | 64 -- patches/20-bump-FB_ORCA_AGENT-version.patch | 11 - update.sh | 41 -- 43 files changed, 117 insertions(+), 3465 deletions(-) delete mode 100644 .travis.yml delete mode 100644 .travis/key delete mode 100755 .travis/obs.sh delete mode 100755 .travis/win32-build.sh delete mode 100755 .travis/win32-deploy.sh delete mode 100755 .travis/win32-install.sh delete mode 100644 MANIFEST_PIDGIN delete mode 100644 MANIFEST_VOIDS delete mode 100644 Makefile.am delete mode 100644 Makefile.mingw delete mode 100644 RELEASE_VERSION delete mode 100644 VERSION delete mode 100755 autogen.sh delete mode 100644 configure.ac create mode 100644 meson.build create mode 100644 meson_options.txt delete mode 100644 patches/01-makefile.patch delete mode 100644 patches/02-glibcompat.patch delete mode 100644 patches/03-plugin.patch delete mode 100644 patches/04-revert-gio.patch delete mode 100644 patches/05-revert-http-callbacks.patch delete mode 100644 patches/06-revert-purple-socket.patch delete mode 100644 patches/07-revert-http-gio.patch delete mode 100644 patches/08-revert-marshaller.patch delete mode 100644 patches/09-revert-http-zlib.patch delete mode 100644 patches/10-glib-compiled-with-debug.patch delete mode 100644 patches/11-contacts.patch delete mode 100644 patches/12-glib-error-cast-bug-2-electric-boogaloo.patch delete mode 100644 patches/13-async-sockets-are-hard.patch delete mode 100644 patches/14-the-future.patch delete mode 100644 patches/15-glib-deprecate-g_type_class_add_private.patch delete mode 100644 patches/16-fix-duplicate-decl-specifier.patch delete mode 100644 patches/17-this-build-system-sucks.patch delete mode 100644 patches/18-fix-thrift-stop-failure.patch delete mode 100644 patches/19-fix-taNewMessage-bug.patch delete mode 100644 patches/20-bump-FB_ORCA_AGENT-version.patch delete mode 100755 update.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4f72f397..00000000 --- a/.travis.yml +++ /dev/null @@ -1,69 +0,0 @@ -language: c - -dist: xenial - -notifications: - email: false - -os: - - linux - -compiler: - - gcc - -env: - global: - - secure: "de18DuRkeapGDbP+1u4+5lgUTeZeVdZVKZdeULv2PbArf+XjAGp1XIRPCLQ0tmrmGZuOb4Py8Hm+mrFgmli+maoUV8dxqN0oLMFgcRyAzwHfHohMNNLn+0VTmxc2S36EHx0iYzPcLU8xADv2fWgG008Ql093UlPivTHB26uC22hdX8HBkF2mtLtlSPrWtJk2Ojr53hCZZCiw0b5SpgFtvcPHkDbmp54XlK4fSGyX0rcLaQHcJZxe7A0hFE4tEB+exxp4UgBckeP7gEQLwmw6XXwt/vQcNXy75qtw0RvrLvnJDMZZM2cBbBcHug+9ORNS5WrSTcJ16Onjq+qZBC++hniJHPtLFqLymmvVt3+qLhSlOyFIxPCPCUMenOiXI1rHyMgAiZFJDwFeInm97rBnS0CKUuZ7/x+8kK/T8pSqjGLXczJ5SDhBZvaBrkyrqB9lGmDFPTK9+v/XwZl6FZncAT1CqlU070ZsfRQJlx8K41OJ534nUaxutPG32BY+2AODmmusYygVVXI0yF6ry5nuWsV6RqBa5wrg8ZQx64yYxk7BPsIpKvCiiBeHfkHZt7C4CDdrQD6+kQVuB13TXI74Uv2olzp3RSRmii8eCU9RtCZeF7GmYFbEG5PPYX5kFKBn4u90ctVBc7Sb5jWOqIMaYjCtmT5+B6lRPrUeemh4P2o=" - - secure: "lgKD/7KlClD77CLnQzvIuIsdDhTwACtRzL0vuqIGBlyclTO+UVC7vIQ6RyOt1Jab9Vg77xItsyRgZqbjDnE/4Vml614rrSfPAaHCBCvf3Z9j7/oqv0maYmX7bhN3/nbkru2LeQMQENZO5CyhYpHcoqPfqEwdryOx4TUj3Tt75QjCfzWr5IM4g8327M+In8NhoovwI+5FMGZUTJZxyAAD8KgdCxnRk8U2c2M096eDObilwEQoD69gM/ZJ8aAyGZkKmGbAkLP8bSx9u6DYAiT3vhRJwopX99u1Y4V8zEik9tqFZYyWlJLrd0wz4EkAN6jdG4rbhyZ33jVr2zMsAnqMeVnB0tLdw7ajWTSOHMwqyK+S8GMDZ87iwQknmE8Zn9wghtJdHTOTJGScFUkS35vOjBXPg2zB6FKP2RAioRBYQfnSV5PWWfADj2j7X6hEsCqO+rdOGq/GPQcRBiveAalq4vcciAaVr01xjU1d7dBSGiMMLWtnWWJfcCwDsJ8lhcNbDXSrfDXRDmO7OsTXzP9Dzc6moJdmOmJVCcdXPjYJGuDHPsDd9qRlx0wXMKzKrHIf/fgwx304EA68OA4j5WkEKcqYYlhzjTG83CKAwLP/8gQ8gsgOeeDeCEyKHN/bmr8Jb7kDYHat34NElEwYpW558rE1U7cNxQtyzaDN7nY/9dg=" - - secure: "WF5OAKWYgKdPO7H6UuLoDupfJyjzJlVxXd3IQR/Dq6eytDF5k5tzsRkqE8BwbpmX51bgbWAhxU+9yeyb7GDNp6koIDltgMYCqt7KZsstKjz7fHSqkjqxmzzXA3reKO/iOz7eddPPrhsliNpsx7iEC8/HWE3hXuyL68SlPzLpFK1h4o2rJMPF9VNmS1hruY21qiuEltxEmqYMR9skMKfddebNChp23pza8i6MWkBIMDsAiZ0ek5WVerWHM8+5/NZx/kKPG0wDThvKsB5lLF8OqlsbBO2AyQMxvQVxHgx6rK3y9GOPiAdVyJHPQHk66hFDkWCkak5xTcbbOJ0P8Qou5A+c/BM9Cyy2W6m4M3BboOD9oso49veAF7p6bcL/QZwEUr/MlJFGfWez9UQaRfESn3kipF/Jhct6OOk+M50DBrVsHgL5ph8y8GJiU7xpKOHeUjM5iB4GJimjotR9u3YYxszbQxjpQ0MG2vnTUNQ1YEnTJ384Y+OQI9KXCjhmxfgymZ3/UyZd9EPnfc8gi2SCr8+xtogA4Bxkz12EEM9vdS195I0dot0OQYlYpZL0HsBMEirsKr049mX6A+Ca8WKUVPqZnqNfbpg80G/F/+hToGTkFoUmOCkpIhwvJb1zMbMLHQhDv1k5YqIcIMg8dY1sxO2IvVsueDBSEqhtZUPNxgQ=" - - secure: "QOIE9TZzTYgFfMPVhwvOEo8buxU0s6/a2+vBsOprAw94juhafpPyiBEUzqyzVLJX+ZY3aVt+s6ch4y4ehEwmyWnqJzxKg6CIy3dGLaBEW+GvZDVtTj1rC3Q/bkPB/MrHkChv08o1Ysb8yQ0gF0GWS+wUQ3m58qgKYMH/ve0Tt76AMBxVU548j1CdKKF1SO6bedCDhX8txiB4MeYWV6pfripYIMcjL5w3fHN8LIw/ctc8APPr2owIJ6rxv8scQXfWWpgCJVxHCZaP7TP5HwLnjFPX+UNpxlH7ZDm+opA9clep5Q1SDmDgUcXT3aMXjRulbzJxcKXXiVNfByEm+Gu85W+c0ka0rXvcKtRZmpwprbiBnMQ+bP/1MqicNkjCLDT/EFMzX0Bo2Kzwu/9Wc/VdAohnIWq03NUsErTL8CGmkI5QhCBJ4nBY1u87wpkOghq95woqxhiC7VI+qV26u0JNBf3miUD5nUfSTS1Ym7BoeEgKFUViDeX7P7LMzKoGfM0+Clutpemq0FuLRP+Ez+4a//UhY42TxUAfdPUCPmGrcm0DlHAa1cZYa2jPcTHOB7trvk31AUgNFCTa36SbLeEym8Z68Z8/8XOLw7Hej8wYSewmc/xwXAq3jNOXh9DAKc4ktOv7EC8fmdCM8qzUDkOzkEcG+tOB7DNPHQi/ZqoNdMk=" - - MY_DEPLOY_BRANCH=master - - pidgindir=/tmp/pidgin - -before_install: - - echo "deb http://download.opensuse.org/repositories/openSUSE:/Tools/xUbuntu_$(lsb_release -rs) ./" - | sudo tee /etc/apt/sources.list.d/suse.list - - curl -s "http://download.opensuse.org/repositories/openSUSE:/Tools/xUbuntu_$(lsb_release -rs)/Release.key" - | sudo apt-key add - - - sudo apt-get update -qq - - sudo apt-get install -qq - --no-install-recommends - binutils-mingw-w64-i686 - clang - gcc-mingw-w64-i686 - libjson-glib-dev - libpurple-dev - osc - - if [ "x$TRAVIS_EVENT_TYPE" != "xpull_request" ]; then - mkdir -p ~/.ssh; - openssl aes-256-cbc - -K $encrypted_1a8668021c4a_key - -iv $encrypted_1a8668021c4a_iv - -in .travis/key -out ~/.ssh/id_rsa -d; - chmod 600 ~/.ssh/id_rsa; - fi - -install: - - .travis/win32-install.sh - -before_script: - - ./update.sh - -script: - - CFLAGS="-Werror" ./autogen.sh --enable-warnings - - make - - make clean - - scan-build -k --status-bugs make - - .travis/win32-build.sh - -after_success: - - if [ "x$TRAVIS_EVENT_TYPE" != "xpull_request" ]; then - .travis/win32-deploy.sh; - .travis/obs.sh; - fi - -after_script: - - if [ "x$TRAVIS_EVENT_TYPE" != "xpull_request" ]; then - shred ~/.ssh/id_rsa; - rm -f ~/.ssh/id_rsa; - fi diff --git a/.travis/key b/.travis/key deleted file mode 100644 index 895439cd608c0a95d5b643902ec51c52fa763916..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1680 zcmV;B25W@*)pf7qpVumJXyDKsTEm-E_%KdGGX7l8olu z&LunYzreDB+JQ+H-zq;J=3B@7Y+qkP?fPZ7gx@RMDkYNtea+^$(?w6d%uV|q-Z z#y;?DQ>G;1qh_KV0|7R`LMcjT>9986QX0{IG|P4(fHs>Na|k9N>WlX|lg=!pqooH! z(G5~LtYOB>I)cv2iD?HT2l8F%-s=y39Zu0;?)nzZaQ|v*#H!>)PXXCg{@$t3Mj?^V zOK6-}z%uW70WR2jUQ<-0qg~}LhC_iVdS9*+WJt@VsX6F$T~xc3-)hh9&85v&wm>%z z0f+G*zom3+H6ah8Bk2a0iA1j}toIbwkqzS$jmvK%%=Tf+JlcQ_r(5H~!LEz-!2WD@ zNg|jAv#57~u!a!Vvk$R8$R|Qi5i@7LOk9+C*(P_$MEra=G3j|jl#$yRv11<6KOP?c zEHUy5Chm%+(O(GhwXluX!S#LVp*z@$*+BMg9@Mk61;O0){{MS^o;qjaPvkzc644}A z9>Q3q6^PEpb(X*xklYWFtQ5wRRoFjG|2NDU85T-2J3+444EFjpT%gZfiD;Y1UY=9F z!zMhkOPY6cz>Ef1UZ(|IImShRC36Ij%&tsvp;u?X5Ud$si2dk>4oKnV_k*u;U;44F4J0@jj zEXi*OX4rEsK_N7d-yP)6*YL%jqr!Ic8wgG1&3xIy`ygM)w-f)5(K>xn3!(m3AZr{zLA<0KArbXJIcI1{|hThxGwyJP54UbHNc~ysnJ7fQ~wyMOH8iP}V}i zcTh=Y!ihNI{z#O67zvd!;8oRbM4gsu_`?HTo6NLfYBPTxCE*7pc1b(l>M+|h$6PZ8 z9`LCMgWNUyroDj_l`PrN0wC0jHkwh)dC3aObx@d12Rpl>%n5vTdemuDewOW*LZxtM$1M;^r+qc*5)Fp{qWF|C4Dky->17 z^9p)nzl&(PnQ(JE%9GFIT`mthP{?Tx5Rlh*NFkSgYjWX5Z_vmhX1cR}XWWB^1Mq(* zcGMcMBH`2Ne3mJhPB<3QXFWCZgJ~S;xB0kzP_dk1Ix);|5x?QVpvZ%xvs>U4QzlEZ z>CE^~QOUk+xctx76O@yaSZ$KG{Ftqh?F*7huSyuGf23j@`UatcusRm|dkh5GxNvzK zq-UIq5|=tPtDAuzb~W0JK3pQ7%ln{NAIW_qD7?^4vxf3AIk*kr|$wUzlhmZLgN67+8a=gUso|uQsUiB_gm(hqe#}BOMbK aY5UKBqzR`NTlVYkTKi8JTPRr{1dDX`V>MU+ diff --git a/.travis/obs.sh b/.travis/obs.sh deleted file mode 100755 index 197cddc7..00000000 --- a/.travis/obs.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -[ "${TRAVIS_PULL_REQUEST}" == "false" -a \ - "${TRAVIS_BRANCH}" == "${MY_DEPLOY_BRANCH}" \ -] || exit -set -e - -GITREV=$(git rev-parse --short=7 HEAD) -FULLVERS="$(date +%Y%m%d)-$(cat RELEASE_VERSION)-${GITREV}-${TRAVIS_BUILD_NUMBER}" -FULLVERS_RPM="$(echo ${FULLVERS} | sed 's/-/~/g')" -FULLDATE=$(date -R) -REPONAME=$(basename "${TRAVIS_REPO_SLUG}") - -git reset -q --hard -git clean -dfqx -./update.sh - -sed -ri \ - -e "20 s/^(\s+).*(,)\$/\1\[${FULLVERS}\]\2/" \ - configure.ac -sed -ri \ - -e "s/(^Build-Depends:.*)/\1, libzephyr4/" \ - debian/control -sed -ri \ - -e "s/(^%setup -q.*)/\1 -n %\{name\}/" \ - -e "s/(^Source0:.*)\-(.*)/\1_\2/" \ - -e "s/(^Version:).*/\1 ${FULLVERS_RPM}/" \ - dist/*.spec - -cat < debian/changelog -${REPONAME} (${FULLVERS}) UNRELEASED; urgency=medium - - * Updated to ${FULLVERS}. - - -- Travis CI ${FULLDATE} -EOF - -cat < ~/.oscrc -[general] -apiurl = https://api.opensuse.org -[https://api.opensuse.org] -user = ${OBSUSER} -pass = ${OBSPASS} -EOF - -mkdir -p m4 -osc checkout "home:${OBSUSER}" "${REPONAME}" -o /tmp/obs - -( - cd /tmp/obs - rm -f *.{dsc,tar.gz} - dpkg-source -I -b "${TRAVIS_BUILD_DIR}" - cp "${TRAVIS_BUILD_DIR}/dist/_service" . - - osc addremove -r - osc commit -m "Updated to ${FULLVERS}" -) diff --git a/.travis/win32-build.sh b/.travis/win32-build.sh deleted file mode 100755 index 992a8a2c..00000000 --- a/.travis/win32-build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -GITREV=$(git rev-parse --short=7 HEAD) -FULLVERS="$(date +%Y%m%d)-$(cat RELEASE_VERSION)-${GITREV}-${TRAVIS_BUILD_NUMBER}" - -CC="i686-w64-mingw32-gcc" \ -DLL_LD_FLAGS="-static-libgcc" \ -make -f Makefile.mingw \ - install \ - GLIB_GENMARSHAL="glib-genmarshal" \ - PLUGIN_VERSION="${FULLVERS}" \ - WIN32_TREE_TOP="win32-dev/pidgin-2.10.11" diff --git a/.travis/win32-deploy.sh b/.travis/win32-deploy.sh deleted file mode 100755 index 85800ac2..00000000 --- a/.travis/win32-deploy.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -[ "${TRAVIS_PULL_REQUEST}" == "false" -a \ - "${TRAVIS_BRANCH}" == "${MY_DEPLOY_BRANCH}" \ -] || exit -set -e - -sftp -qo StrictHostKeyChecking=no -P ${SSHPORT} ${SSHUSER} -b < (original author) -dequis (current maintainer) +dequis (previous maintainer) +James Carthew (hacking current fork) diff --git a/ChangeLog b/ChangeLog index 98551d94..4d0455c9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +purple-facebook (2024-04-01): + - Updated build system to Meson. + - Scrapped Mercurial and Autotools. + - Merged all patches into tree. + purple-facebook-56d191003b34 (2016-09-15): - Update backport to handle Gio port diff --git a/MANIFEST_PIDGIN b/MANIFEST_PIDGIN deleted file mode 100644 index d6d934c7..00000000 --- a/MANIFEST_PIDGIN +++ /dev/null @@ -1,25 +0,0 @@ -AUTHORS -COPYING -COPYRIGHT -libpurple/glibcompat.h -libpurple/http.c -libpurple/http.h -libpurple/protocols/facebook/api.c -libpurple/protocols/facebook/api.h -libpurple/protocols/facebook/data.c -libpurple/protocols/facebook/data.h -libpurple/protocols/facebook/facebook.c -libpurple/protocols/facebook/facebook.h -libpurple/protocols/facebook/http.c -libpurple/protocols/facebook/http.h -libpurple/protocols/facebook/id.h -libpurple/protocols/facebook/json.c -libpurple/protocols/facebook/json.h -libpurple/protocols/facebook/Makefile.am -libpurple/protocols/facebook/Makefile.mingw -libpurple/protocols/facebook/mqtt.c -libpurple/protocols/facebook/mqtt.h -libpurple/protocols/facebook/thrift.c -libpurple/protocols/facebook/thrift.h -libpurple/protocols/facebook/util.c -libpurple/protocols/facebook/util.h diff --git a/MANIFEST_VOIDS b/MANIFEST_VOIDS deleted file mode 100644 index b1b08a6e..00000000 --- a/MANIFEST_VOIDS +++ /dev/null @@ -1,12 +0,0 @@ -include/blistnode.h -include/buddy.h -include/buddylist.h -include/conversations.h -include/conversationtypes.h -include/image.h -include/image-store.h -include/message.h -include/plugins.h -include/presence.h -include/protocol.h -include/protocols.h diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index 9e638c50..00000000 --- a/Makefile.am +++ /dev/null @@ -1,17 +0,0 @@ -ACLOCAL_AMFLAGS = -Im4 -SUBDIRS = pidgin/libpurple/protocols/facebook - -EXTRA_DIST = \ - autogen.sh \ - debian \ - include \ - Makefile.mingw \ - MANIFEST_PIDGIN \ - MANIFEST_VOIDS \ - patches \ - pidgin/AUTHORS \ - pidgin/COPYING \ - pidgin/COPYRIGHT \ - update.sh \ - VERSION \ - RELEASE_VERSION diff --git a/Makefile.mingw b/Makefile.mingw deleted file mode 100644 index ecc91208..00000000 --- a/Makefile.mingw +++ /dev/null @@ -1,66 +0,0 @@ -PLUGIN_VERSION = $(shell cat RELEASE_VERSION) -PURPLE_VERSION = 2.10.11 - -TREE_TOP = ../../../.. -PLUGIN_TOP = pidgin/libpurple/protocols/facebook -WIN32_TREE_TOP := ../win32-dev/pidgin-$(PURPLE_VERSION) - -PIDGIN_TREE_TOP = $(TREE_TOP)/$(WIN32_TREE_TOP) -WIN32_DEV_TOP = $(PIDGIN_TREE_TOP)/.. -PURPLE_TOP = $(TREE_TOP)/$(WIN32_TREE_TOP)/libpurple -PURPLE_INSTALL_PLUGINS_DIR = $(TREE_TOP)/win32-install-dir/plugins - -GTK_TOP = $(WIN32_DEV_TOP)/glib-2.28.8 -GTK_BIN = $(GTK_TOP)/bin -GLIB_GENMARSHAL = $(GTK_BIN)/glib-genmarshal -JSON_GLIB_TOP = $(WIN32_DEV_TOP)/json-glib-0.14 - -CFLAGS = -O0 -g -export CFLAGS += -include purple-compat.h - -export DEFINES = $(AUTOTOOLS_DEFINES) \ - -DPACKAGE_NAME=\"purple-facebook\" \ - -DPACKAGE_TARNAME=\"purple-facebook\" \ - -DPACKAGE_VERSION=\"$(PLUGIN_VERSION)\" \ - -DPACKAGE_STRING=\"purple-facebook\ $(PLUGIN_VERSION)\" \ - -DPACKAGE_BUGREPORT=\"https://github.com/dequis/purple-facebook/issues\" \ - -DPACKAGE_URL=\"https://github.com/dequis/purple-facebook\" \ - -DPACKAGE=\"purple-facebook\" \ - -DVERSION=\"$(PLUGIN_VERSION)\" \ - -DPURPLE_PLUGINS - -export INCLUDE_PATHS = \ - -I$(TREE_TOP)/include \ - -I$(TREE_TOP)/pidgin \ - -I$(TREE_TOP)/pidgin/libpurple - -export LIB_PATHS = \ - -L$(PIDGIN_TREE_TOP)/win32-install-dir - -override PLUGIN_MAKE = \ -$(MAKE) -C $(PLUGIN_TOP) -f Makefile.mingw $(@) \ - PURPLE_VERSION="$(PURPLE_VERSION)" \ - PIDGIN_TREE_TOP="$(PIDGIN_TREE_TOP)" \ - WIN32_DEV_TOP="$(WIN32_DEV_TOP)" \ - PLUGIN_TOP="$(PLUGIN_TOP)" \ - PURPLE_TOP="$(PURPLE_TOP)" \ - PURPLE_INSTALL_PLUGINS_DIR="$(PURPLE_INSTALL_PLUGINS_DIR)" \ - GPLUGIN_TOP="." \ - GTK_TOP="$(GTK_TOP)" \ - GTK_BIN="$(GTK_BIN)" \ - GLIB_GENMARSHAL="$(GLIB_GENMARSHAL)" \ - JSON_GLIB_TOP="$(JSON_GLIB_TOP)" - -.PHONY: all install clean - -all: - $(call PLUGIN_MAKE,all) - -$(PURPLE_INSTALL_PLUGINS_DIR): - cd $(PLUGIN_TOP) && mkdir -p $(PURPLE_INSTALL_PLUGINS_DIR) - -install: $(PURPLE_INSTALL_PLUGINS_DIR) - $(call PLUGIN_MAKE,install) - -clean: - $(call PLUGIN_MAKE,clean) diff --git a/README b/README index bb181fcd..25c5aee3 100644 --- a/README +++ b/README @@ -4,4 +4,8 @@ this plugin is back-ported for purple2. This project is not affiliated with Facebook, Inc. +This has been forked from https://github.com/dequis/purple-facebook/ - Thanks Dequis for Maintaining this project for so long. + +Build Instructions: git clone the repository, run meson setup build, cd into build directory and type ninja; ninja install to install to plugins folder. + More information: https://github.com/dequis/purple-facebook/wiki diff --git a/RELEASE_VERSION b/RELEASE_VERSION deleted file mode 100644 index 85b7c695..00000000 --- a/RELEASE_VERSION +++ /dev/null @@ -1 +0,0 @@ -0.9.6 diff --git a/VERSION b/VERSION deleted file mode 100644 index 7a8181f5..00000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -9ff9acf9fa14 diff --git a/autogen.sh b/autogen.sh deleted file mode 100755 index f5c04909..00000000 --- a/autogen.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -test -z "$srcdir" && srcdir=$(dirname "$0") -test -z "$srcdir" && srcdir=. - -cwd=$(pwd) -cd "$srcdir" - -./update.sh || exit $? - -mkdir -p m4 -autoreconf --verbose --force --install || exit $? - -cd "$cwd" -test -z "$NOCONFIGURE" && "$srcdir/configure" $@ diff --git a/configure.ac b/configure.ac deleted file mode 100644 index db3ed51e..00000000 --- a/configure.ac +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 2015-2016 James Geboski -# -# 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, see . - -AC_PREREQ([2.64]) - -AC_INIT( - [purple-facebook], - m4_esyscmd_s([cat RELEASE_VERSION]), - [https://github.com/dequis/purple-facebook/issues], - [purple-facebook], - [https://github.com/dequis/purple-facebook], - [] -) - -AC_CONFIG_AUX_DIR([build-aux]) -AC_CONFIG_MACRO_DIR([m4]) -AM_INIT_AUTOMAKE([subdir-objects]) - -AC_PROG_CC -AM_PROG_CC_C_O - -AC_DISABLE_STATIC -AC_PROG_LIBTOOL - -m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) - -# Define PKG_CHECK_VAR() for pkg-config < 0.28 -m4_define_default( - [PKG_CHECK_VAR], - [AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config]) - AS_IF([test -z "$$1"], [$1=`$PKG_CONFIG --variable="$3" "$2"`]) - AS_IF([test -n "$$1"], [$4], [$5])] -) - -AC_ARG_ENABLE( - [warnings], - [AS_HELP_STRING( - [--enable-warnings], - [Enable additional compile-time (GCC) warnings] - )], - [WARNINGS="yes"], - [WARNINGS="no"] -) - -AS_IF( - [test "x$WARNINGS" == "xyes"], - [CFLAGS="$CFLAGS -Wall -Wextra \ - -Waggregate-return \ - -Wdeclaration-after-statement \ - -Wfloat-equal \ - -Wformat \ - -Winit-self \ - -Wmissing-declarations \ - -Wmissing-prototypes \ - -Wno-unused-parameter \ - -Wpointer-arith"] -) - -AC_ARG_WITH( - [plugindir], - [AS_HELP_STRING( - [--with-plugindir], - [libpurple plugin directory] - )], - [PURPLE_PLUGINDIR="$with_plugindir"] -) - -PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28.0 gio-2.0 gobject-2.0]) -PKG_CHECK_MODULES([JSON], [json-glib-1.0 >= 0.14.0]) -PKG_CHECK_MODULES([PURPLE], [purple < 3]) -PKG_CHECK_MODULES([ZLIB], [zlib]) - -PKG_CHECK_VAR([GLIB_GENMARSHAL], [glib-2.0], [glib_genmarshal]) -AS_IF( - [test -z "$GLIB_GENMARSHAL"], - [AC_MSG_ERROR([The `glib-genmarshal' tool is missing.])] -) - -AS_IF( - [test -z "$PURPLE_PLUGINDIR"], - [PKG_CHECK_VAR( - [PURPLE_PLUGINDIR], - [purple], - [plugindir], - [PURPLE_PLUGINDIR="$PURPLE_PLUGINDIR"], - [PURPLE_PLUGINDIR="$libdir/purple-2"] - )] -) - -PLUGIN_CFLAGS="-I`pwd`/$srcdir/include -I`pwd`/$srcdir/pidgin " -PLUGIN_CFLAGS="${PLUGIN_CFLAGS} -I`pwd`/$srcdir/pidgin/libpurple" -PLUGIN_CFLAGS="$PLUGIN_CFLAGS -DPURPLE_PLUGINS -include purple-compat.h" -AC_SUBST([PLUGIN_CFLAGS]) - -AM_CONDITIONAL([STATIC_FACEBOOK], false) -AC_SUBST([PLUGIN_LDFLAGS], [-avoid-version]) -AC_SUBST([PURPLE_LIBS], ["$GLIB_LIBS $JSON_LIBS $PURPLE_LIBS $ZLIB_LIBS"]) - -AC_CONFIG_FILES([Makefile pidgin/libpurple/protocols/facebook/Makefile]) -AC_OUTPUT diff --git a/debian/control b/debian/control index ce127ff3..ba9c89dd 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Maintainer: jgeboski Section: misc Priority: optional Standards-Version: 3.9.6 -Build-Depends: debhelper (>= 9), dh-autoreconf, libglib2.0-dev (>= 2.28), libjson-glib-dev (>= 0.14), libpurple-dev +Build-Depends: debhelper (>= 9), meson, libglib2.0-dev (>= 2.28), libjson-glib-dev (>= 0.14), libpurple-dev Homepage: https://github.com/jgeboski/purple-facebook Package: purple-facebook diff --git a/debian/rules b/debian/rules index bd66c750..6b11501d 100755 --- a/debian/rules +++ b/debian/rules @@ -1,8 +1,4 @@ #!/usr/bin/make -f -override_dh_auto_install: - dh_auto_install - find debian -name '*.la' -print -delete - %: - dh $@ --with autoreconf + dh build --buildsystem=meson diff --git a/dist/purple-facebook.spec b/dist/purple-facebook.spec index 401ef15e..0d927999 100644 --- a/dist/purple-facebook.spec +++ b/dist/purple-facebook.spec @@ -13,12 +13,10 @@ License: GPL-2.0+ URL: https://github.com/dequis/purple-facebook Source0: %{name}-%{version}.tar.gz -BuildRequires: autoconf >= 2.64 -BuildRequires: automake +BuildRequires: meson BuildRequires: glib2-devel >= 2.28.0 BuildRequires: json-glib-devel >= 0.14 BuildRequires: libpurple-devel < 3 -BuildRequires: libtool BuildRequires: pkg-config Requires: glib2 >= 2.28.0 diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..4eb5ca84 --- /dev/null +++ b/meson.build @@ -0,0 +1,93 @@ +project('purple-facebook', 'c') + +# Define package version +#PACKAGE_VERSION = '0.9.61' # Adjust the version number as needed +#PACKAGE_URL = 'https://github.com/DMJC/purple-facebook' + +# Configure the package +#pkgconf = configuration_data() +#pkgconf.set('PACKAGE_VERSION', PACKAGE_VERSION) +#pkgconf.set('PACKAGE_URL', PACKAGE_URL) + +compiler_options = [] +compiler_options += ['-DPACKAGE_VERSION="0.9.7"'] +compiler_options += ['-DPACKAGE_URL="https://github.com/DMJC/purple-facebook"'] + +#define('PACKAGE_VERSION', '"0.9.61"') +#define('PACKAGE_URL', '"https://github.com/DMJC/purple-facebook"') + +# Write the configuration to a file +#configure_file(output : 'config.h', +# configuration : pkgconf) + +# Add compiler flags if warnings are enabled +if get_option('warnings') + add_project_arguments('-Wall', '-Wextra', '-Waggregate-return', '-Wdeclaration-after-statement', '-Wfloat-equal', '-Wformat', '-Winit-self', '-Wmissing-declarations', '-Wmissing-prototypes', '-Wno-unused-parameter', '-Wpointer-arith', language : 'c') +endif + +# Define source files +source_files = files( + 'pidgin/libpurple/protocols/facebook/marshal.c', + 'pidgin/libpurple/protocols/facebook/api.c', + 'pidgin/libpurple/protocols/facebook/data.c', + 'pidgin/libpurple/protocols/facebook/facebook.c', + 'pidgin/libpurple/protocols/facebook/http.c', + 'pidgin/libpurple/protocols/facebook/json.c', + 'pidgin/libpurple/protocols/facebook/mqtt.c', + 'pidgin/libpurple/protocols/facebook/thrift.c', + 'pidgin/libpurple/protocols/facebook/util.c', + 'pidgin/libpurple/http.c', + 'pidgin/libpurple/purple-socket.c' +) + +# Argument for specifying the libpurple plugin directory +plugin_dir = get_option('plugindir') + +plugin_cflags = ['-I@0@/include'.format(meson.current_source_dir()), + '-I@0@/pidgin'.format(meson.current_source_dir()), + '-I@0@/pidgin/libpurple'.format(meson.current_source_dir()), + '-Ipidgin/libpurple/', # Adjust this path + '-DPURPLE_PLUGINS', + '-include', 'purple-compat.h'] +add_project_arguments(plugin_cflags, language : 'c') + +# Dependency checks using pkg-config +glib_dep = dependency('glib-2.0', version : '>=2.28.0') +json_dep = dependency('json-glib-1.0', version : '>=0.14.0') +purple_dep = dependency('purple', version : '<3') +zlib_dep = dependency('zlib') + +# Check for glib-genmarshal tool +glib_genmarshal = find_program('glib-genmarshal') +if not glib_genmarshal.found() + error('The `glib-genmarshal` tool is missing.') +endif + +generate_marshal_header_command = ['glib-genmarshal', 'pidgin/libpurple/protocols/facebook/marshaller.list', '--prefix=fb_marshal', '--header', '--output', 'pidgin/libpurple/protocols/facebook/marshal.h'] +generate_marshal_c_command = ['glib-genmarshal', 'pidgin/libpurple/protocols/facebook/marshaller.list', '--body', '--prefix=fb_marshal', '--output', 'pidgin/libpurple/protocols/facebook/marshal.c'] +run_command(generate_marshal_header_command, + check: true) + +run_command(generate_marshal_c_command, + check: true) + +# Determine the plugin directory if not provided by the user +if plugin_dir == '' + purple_plugindir = '' + if purple_dep.found() + purple_plugindir = purple_dep.get_pkgconfig_variable('plugindir') + endif + if purple_plugindir == '' + purple_plugindir = join_paths(get_option('libdir'), 'purple-2') + endif +else + purple_plugindir = plugin_dir +endif + + + +# Define library +#plugin_library = shared_library('purple-facebook-plugin', source_files, dependencies : [glib_dep, json_dep, purple_dep, zlib_dep], c_args: compiler_options) + +plugin_library = shared_library('purple-facebook-plugin', source_files, dependencies : [glib_dep, json_dep, purple_dep, zlib_dep], c_args: compiler_options, install: true, install_dir: purple_plugindir) + diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 00000000..74a03e1b --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,10 @@ +option('warnings', + type : 'boolean', + description : 'Enable additional compile-time (GCC) warnings', + value : true) + +option('plugindir', + type : 'string', + description : 'Specify the libpurple plugin directory', + value : '') + diff --git a/patches/01-makefile.patch b/patches/01-makefile.patch deleted file mode 100644 index ed5a3d7e..00000000 --- a/patches/01-makefile.patch +++ /dev/null @@ -1,68 +0,0 @@ -diff -r ad2ee74b913a libpurple/protocols/facebook/Makefile.am ---- a/libpurple/protocols/facebook/Makefile.am Thu Jan 07 14:06:04 2016 -0500 -+++ b/libpurple/protocols/facebook/Makefile.am Thu Jan 07 15:45:09 2016 -0500 -@@ -20,7 +20,12 @@ - thrift.c \ - thrift.h \ - util.c \ -- util.h -+ util.h \ -+ ../../glibcompat.h \ -+ ../../http.c \ -+ ../../http.h \ -+ ../../purple-socket.h \ -+ ../../purple-socket.c - - AM_CFLAGS = $(st) - -@@ -43,10 +48,9 @@ - endif - - AM_CPPFLAGS = \ -- -I$(top_srcdir)/libpurple \ -- -I$(top_builddir)/libpurple \ -- -I$(top_srcdir) \ - $(GLIB_CFLAGS) \ - $(JSON_CFLAGS) \ -- $(GPLUGIN_CFLAGS) \ -+ $(PURPLE_CFLAGS) \ -+ $(ZLIB_CFLAGS) \ -+ $(PLUGIN_CFLAGS) \ - $(DEBUG_CFLAGS) -diff -r ad2ee74b913a libpurple/protocols/facebook/Makefile.mingw ---- a/libpurple/protocols/facebook/Makefile.mingw Thu Jan 07 14:06:04 2016 -0500 -+++ b/libpurple/protocols/facebook/Makefile.mingw Thu Jan 07 15:45:09 2016 -0500 -@@ -49,7 +49,9 @@ - json.c \ - mqtt.c \ - thrift.c \ -- util.c -+ util.c \ -+ ../../http.c \ -+ ../../purple-socket.c - - OBJECTS = $(C_SRC:%.c=%.o) - -@@ -62,7 +64,6 @@ - -lgobject-2.0 \ - -lws2_32 \ - -lintl \ -- -lgplugin \ - -ljson-glib-1.0 \ - -lz \ - -lpurple -@@ -76,12 +77,12 @@ - - all: $(TARGET).dll - --install: all $(DLL_INSTALL_DIR) -+install: all - cp $(TARGET).dll $(DLL_INSTALL_DIR) - - $(OBJECTS): $(PURPLE_CONFIG_H) - --$(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS) -+$(TARGET).dll: $(OBJECTS) - $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll - - ## diff --git a/patches/02-glibcompat.patch b/patches/02-glibcompat.patch deleted file mode 100644 index 5e131f0f..00000000 --- a/patches/02-glibcompat.patch +++ /dev/null @@ -1,33 +0,0 @@ ---- a/libpurple/glibcompat.h 2016-11-21 09:38:39.250858177 -0300 -+++ b/libpurple/glibcompat.h 2016-11-21 09:39:44.789845560 -0300 -@@ -71,6 +71,30 @@ - /****************************************************************************** - * g_assert_* macros - *****************************************************************************/ -+ -+#if !GLIB_CHECK_VERSION(2, 32, 0) -+static inline GByteArray * g_byte_array_new_take(guint8 *data, gsize len) -+{ -+ GByteArray *array; -+ -+ array = g_byte_array_new(); -+ g_byte_array_append(array, data, len); -+ g_free(data); -+ -+ return array; -+} -+ -+static inline void g_queue_free_full(GQueue *queue, GDestroyNotify free_func) -+{ -+ g_queue_foreach(queue, (GFunc)free_func, NULL); -+ g_queue_free(queue); -+} -+#endif -+ -+#if !GLIB_CHECK_VERSION(2, 30, 0) -+#define G_VALUE_INIT {0, {{0}}} -+#endif -+ - #if !GLIB_CHECK_VERSION(2, 38, 0) - #define g_assert_true(expr) G_STMT_START { \ - if G_LIKELY (expr) ; else \ diff --git a/patches/03-plugin.patch b/patches/03-plugin.patch deleted file mode 100644 index 0964223c..00000000 --- a/patches/03-plugin.patch +++ /dev/null @@ -1,398 +0,0 @@ -diff -r 25a255f32eee libpurple/protocols/facebook/facebook.c ---- a/libpurple/protocols/facebook/facebook.c Sat Jan 16 10:01:23 2016 -0500 -+++ b/libpurple/protocols/facebook/facebook.c Mon Jan 18 09:39:59 2016 -0500 -@@ -500,7 +500,7 @@ - id = purple_image_store_add_weak(pimg); - - g_free(msg->text); -- msg->text = g_strdup_printf("text = g_strdup_printf("", id); - msg->flags |= FB_API_MESSAGE_FLAG_DONE; -@@ -966,7 +966,7 @@ - GSList *select = NULL; - PurpleConnection *gc; - -- if (!PURPLE_IS_BUDDY(node)) { -+ if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { - return; - } - -@@ -1160,7 +1160,7 @@ - PurpleConnection *gc; - PurpleMenuAction *act; - -- if (!PURPLE_IS_BUDDY(node)) { -+ if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { - return NULL; - } - -@@ -1208,7 +1208,8 @@ - } - - static gint --fb_im_send(PurpleConnection *gc, PurpleMessage *msg) -+fb_im_send(PurpleConnection *gc, const gchar *who, const gchar *tmsg, -+ PurpleMessageFlags flags) - { - const gchar *name; - const gchar *text; -@@ -1217,6 +1218,8 @@ - FbId uid; - gchar *sext; - -+ PurpleMessage *msg = purple_message_new_outgoing(who, tmsg, flags); -+ - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); - -@@ -1355,7 +1358,8 @@ - } - - static gint --fb_chat_send(PurpleConnection *gc, gint id, PurpleMessage *msg) -+fb_chat_send(PurpleConnection *gc, gint id, const gchar *tmsg, -+ PurpleMessageFlags flags) - { - const gchar *name; - const gchar *text; -@@ -1366,6 +1370,8 @@ - PurpleAccount *acct; - PurpleChatConversation *chat; - -+ PurpleMessage *msg = purple_message_new_outgoing(NULL, tmsg, flags); -+ - acct = purple_connection_get_account(gc); - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); -@@ -1534,107 +1540,6 @@ - } - - static void --facebook_protocol_init(PurpleProtocol *protocol) --{ -- GList *opts = NULL; -- PurpleAccountOption *opt; -- -- protocol->id = FB_PROTOCOL_ID; -- protocol->name = "Facebook"; -- protocol->options = OPT_PROTO_CHAT_TOPIC; -- -- opt = purple_account_option_int_new(_("Buddy list sync interval"), -- "sync-interval", 5); -- opts = g_list_prepend(opts, opt); -- -- opt = purple_account_option_bool_new(_("Mark messages as read on focus"), -- "mark-read", TRUE); -- opts = g_list_prepend(opts, opt); -- -- opt = purple_account_option_bool_new(_("Mark messages as read only when available"), -- "mark-read-available", FALSE); -- opts = g_list_prepend(opts, opt); -- -- opt = purple_account_option_bool_new(_("Show self messages"), -- "show-self", TRUE); -- opts = g_list_prepend(opts, opt); -- -- opt = purple_account_option_bool_new(_("Show unread messages"), -- "show-unread", TRUE); -- opts = g_list_prepend(opts, opt); -- -- opt = purple_account_option_bool_new(_("Open new group chats with " -- "incoming messages"), -- "group-chat-open", TRUE); -- opts = g_list_prepend(opts, opt); -- protocol->account_options = g_list_reverse(opts); --} -- --static void --facebook_protocol_class_init(PurpleProtocolClass *klass) --{ -- klass->login = fb_login; -- klass->close = fb_close; -- klass->status_types = fb_status_types; -- klass->list_icon = fb_list_icon; --} -- --static void --facebook_protocol_client_iface_init(PurpleProtocolClientIface *iface) --{ -- iface->tooltip_text = fb_client_tooltip_text; -- iface->blist_node_menu = fb_client_blist_node_menu; -- iface->offline_message = fb_client_offline_message; --} -- --static void --facebook_protocol_server_iface_init(PurpleProtocolServerIface *iface) --{ -- iface->set_status = fb_server_set_status; --} -- --static void --facebook_protocol_im_iface_init(PurpleProtocolIMIface *iface) --{ -- iface->send = fb_im_send; -- iface->send_typing = fb_im_send_typing; --} -- --static void --facebook_protocol_chat_iface_init(PurpleProtocolChatIface *iface) --{ -- iface->info = fb_chat_info; -- iface->info_defaults = fb_chat_info_defaults; -- iface->join = fb_chat_join; -- iface->get_name = fb_chat_get_name; -- iface->invite = fb_chat_invite; -- iface->send = fb_chat_send; -- iface->set_topic = fb_chat_set_topic; --} -- --static void --facebook_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *iface) --{ -- iface->get_list = fb_roomlist_get_list; -- iface->cancel = fb_roomlist_cancel; --} -- --PURPLE_DEFINE_TYPE_EXTENDED( -- FacebookProtocol, facebook_protocol, PURPLE_TYPE_PROTOCOL, 0, -- -- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE, -- facebook_protocol_client_iface_init) -- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE, -- facebook_protocol_server_iface_init) -- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE, -- facebook_protocol_im_iface_init) -- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE, -- facebook_protocol_chat_iface_init) -- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE, -- facebook_protocol_roomlist_iface_init) --); -- --static void - fb_cmds_register(void) - { - PurpleCmdId id; -@@ -1646,13 +1551,13 @@ - g_return_if_fail(fb_cmds == NULL); - - id = purple_cmd_register("kick", "s", PURPLE_CMD_P_PROTOCOL, cflags, -- fb_protocol->id, fb_cmd_kick, -+ "prpl-facebook", fb_cmd_kick, - _("kick: Kick someone from the chat"), - NULL); - fb_cmds = g_slist_prepend(fb_cmds, GUINT_TO_POINTER(id)); - - id = purple_cmd_register("leave", "", PURPLE_CMD_P_PROTOCOL, cflags, -- fb_protocol->id, fb_cmd_leave, -+ "prpl-facebook", fb_cmd_leave, - _("leave: Leave the chat"), - NULL); - fb_cmds = g_slist_prepend(fb_cmds, GUINT_TO_POINTER(id)); -@@ -1671,43 +1576,110 @@ - g_slist_free_full(fb_cmds, fb_cmds_unregister_free); - } - --static PurplePluginInfo * --plugin_query(GError **error) --{ -- return purple_plugin_info_new( -- "id", FB_PROTOCOL_ID, -- "name", "Facebook Protocol", -- "version", DISPLAY_VERSION, -- "category", N_("Protocol"), -- "summary", N_("Facebook Protocol Plugin"), -- "description", N_("Facebook Protocol Plugin"), -- "website", PURPLE_WEBSITE, -- "abi-version", PURPLE_ABI_VERSION, -- "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL | -- PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD, -- NULL -- ); --} -- - static gboolean --plugin_load(PurplePlugin *plugin, GError **error) -+plugin_load(PurplePlugin *plugin) - { -- facebook_protocol_register_type(plugin); -- fb_protocol = purple_protocols_add(FACEBOOK_TYPE_PROTOCOL, error); -- -- if (fb_protocol == NULL) { -- return FALSE; -- } -- - fb_cmds_register(); -+ _purple_socket_init(); -+ purple_http_init(); - return TRUE; - } - - static gboolean --plugin_unload(PurplePlugin *plugin, GError **error) -+plugin_unload(PurplePlugin *plugin) - { - fb_cmds_unregister(); -- return purple_protocols_remove(fb_protocol, error); -+ purple_http_uninit(); -+ _purple_socket_uninit(); -+ return TRUE; - } - --PURPLE_PLUGIN_INIT(facebook, plugin_query, plugin_load, plugin_unload); -+G_MODULE_EXPORT gboolean -+purple_init_plugin(PurplePlugin *plugin); -+ -+G_MODULE_EXPORT gboolean -+purple_init_plugin(PurplePlugin *plugin) -+{ -+ GList *opts = NULL; -+ PurpleAccountOption *opt; -+ -+ static gboolean inited = FALSE; -+ static PurplePluginInfo info; -+ static PurplePluginProtocolInfo pinfo; -+ -+ (void) fb_protocol; -+ plugin->info = &info; -+ -+ if (G_LIKELY(inited)) { -+ return purple_plugin_register(plugin); -+ } -+ -+ memset(&info, 0, sizeof info); -+ memset(&pinfo, 0, sizeof pinfo); -+ -+ info.magic = PURPLE_PLUGIN_MAGIC; -+ info.major_version = PURPLE_MAJOR_VERSION; -+ info.minor_version = PURPLE_MINOR_VERSION; -+ info.type = PURPLE_PLUGIN_PROTOCOL; -+ info.priority = PURPLE_PRIORITY_DEFAULT; -+ info.id = FB_PROTOCOL_ID; -+ info.name = "Facebook"; -+ info.version = PACKAGE_VERSION; -+ info.summary = N_("Facebook Protocol Plugin"); -+ info.description = N_("Facebook Protocol Plugin"); -+ info.homepage = PACKAGE_URL; -+ info.load = plugin_load; -+ info.unload = plugin_unload; -+ info.extra_info = &pinfo; -+ -+ pinfo.options = OPT_PROTO_CHAT_TOPIC; -+ pinfo.list_icon = fb_list_icon; -+ pinfo.tooltip_text = fb_client_tooltip_text; -+ pinfo.status_types = fb_status_types; -+ pinfo.blist_node_menu = fb_client_blist_node_menu; -+ pinfo.chat_info = fb_chat_info; -+ pinfo.chat_info_defaults = fb_chat_info_defaults; -+ pinfo.login = fb_login; -+ pinfo.close = fb_close; -+ pinfo.send_im = fb_im_send; -+ pinfo.send_typing = fb_im_send_typing; -+ pinfo.set_status = fb_server_set_status; -+ pinfo.join_chat = fb_chat_join; -+ pinfo.get_chat_name = fb_chat_get_name; -+ pinfo.chat_invite = fb_chat_invite; -+ pinfo.chat_send = fb_chat_send; -+ pinfo.set_chat_topic = fb_chat_set_topic; -+ pinfo.roomlist_get_list = fb_roomlist_get_list; -+ pinfo.roomlist_cancel = fb_roomlist_cancel; -+ pinfo.offline_message = fb_client_offline_message; -+ pinfo.struct_size = sizeof pinfo; -+ -+ opt = purple_account_option_int_new(_("Buddy list sync interval"), -+ "sync-interval", 5); -+ opts = g_list_prepend(opts, opt); -+ -+ opt = purple_account_option_bool_new(_("Mark messages as read on focus"), -+ "mark-read", TRUE); -+ opts = g_list_prepend(opts, opt); -+ -+ opt = purple_account_option_bool_new(_("Mark messages as read only when available"), -+ "mark-read-available", FALSE); -+ opts = g_list_prepend(opts, opt); -+ -+ opt = purple_account_option_bool_new(_("Show self messages"), -+ "show-self", TRUE); -+ opts = g_list_prepend(opts, opt); -+ -+ opt = purple_account_option_bool_new(_("Show unread messages"), -+ "show-unread", TRUE); -+ opts = g_list_prepend(opts, opt); -+ -+ opt = purple_account_option_bool_new(_("Open new group chats with " -+ "incoming messages"), -+ "group-chat-open", TRUE); -+ opts = g_list_prepend(opts, opt); -+ pinfo.protocol_options = g_list_reverse(opts); -+ -+ inited = TRUE; -+ return purple_plugin_register(plugin); -+} -diff -r 25a255f32eee libpurple/protocols/facebook/facebook.h ---- a/libpurple/protocols/facebook/facebook.h Sat Jan 16 10:01:23 2016 -0500 -+++ b/libpurple/protocols/facebook/facebook.h Mon Jan 18 09:39:59 2016 -0500 -@@ -22,24 +22,7 @@ - #ifndef _FACEBOOK_H_ - #define _FACEBOOK_H_ - --/** -- * SECTION:facebook -- * @section_id: facebook-plugin -- * @short_description: facebook.h -- * @title: Facebook Plugin -- * -- * The Facebook Messenger #PurpleProtocol. -- */ -- - #include --#include -- --#define FACEBOOK_TYPE_PROTOCOL (facebook_protocol_get_type()) --#define FACEBOOK_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FACEBOOK_TYPE_PROTOCOL, FacebookProtocol)) --#define FACEBOOK_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FACEBOOK_TYPE_PROTOCOL, FacebookProtocolClass)) --#define FACEBOOK_IS_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FACEBOOK_TYPE_PROTOCOL)) --#define FACEBOOK_IS_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FACEBOOK_TYPE_PROTOCOL)) --#define FACEBOOK_PROTOCOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FACEBOOK_TYPE_PROTOCOL, FacebookProtocolClass)) - - /** - * FB_PROTOCOL_ID: -@@ -48,37 +31,4 @@ - */ - #define FB_PROTOCOL_ID "prpl-facebook" - --typedef struct _FacebookProtocol FacebookProtocol; --typedef struct _FacebookProtocolClass FacebookProtocolClass; -- --/** -- * FacebookProtocol: -- * -- * Represents the Facebook #PurpleProtocol. -- */ --struct _FacebookProtocol --{ -- /*< private >*/ -- PurpleProtocol parent; --}; -- --/** -- * FacebookProtocolClass: -- * -- * The base class for all #FacebookProtocol's. -- */ --struct _FacebookProtocolClass --{ -- /*< private >*/ -- PurpleProtocolClass parent_class; --}; -- --/** -- * facebook_protocol_get_type: -- * -- * Returns: The #GType for a #FacebookProtocol. -- */ --G_MODULE_EXPORT GType --facebook_protocol_get_type(void); -- - #endif /* _FACEBOOK_H_ */ diff --git a/patches/04-revert-gio.patch b/patches/04-revert-gio.patch deleted file mode 100644 index 811c3171..00000000 --- a/patches/04-revert-gio.patch +++ /dev/null @@ -1,530 +0,0 @@ -diff -r 56d191003b34 -r 5a63c26f21fd libpurple/protocols/facebook/facebook.c ---- a/libpurple/protocols/facebook/facebook.c Thu Sep 15 13:31:06 2016 -0500 -+++ b/libpurple/protocols/facebook/facebook.c Wed Sep 14 14:53:12 2016 -0500 -@@ -373,8 +373,8 @@ - - gc = fb_data_get_connection(fata); - -- if (error->domain == G_IO_ERROR) { -- purple_connection_g_error(gc, error); -+ if (error->domain == FB_MQTT_SSL_ERROR) { -+ purple_connection_ssl_error(gc, error->code); - return; - } - -diff -r 56d191003b34 -r 5a63c26f21fd libpurple/protocols/facebook/mqtt.c ---- a/libpurple/protocols/facebook/mqtt.c Thu Sep 15 13:31:06 2016 -0500 -+++ b/libpurple/protocols/facebook/mqtt.c Wed Sep 14 14:53:12 2016 -0500 -@@ -28,8 +28,7 @@ - #include "account.h" - #include "eventloop.h" - #include "glibcompat.h" --#include "purple-gio.h" --#include "queuedoutputstream.h" -+#include "sslconn.h" - - #include "mqtt.h" - #include "util.h" -@@ -37,17 +36,17 @@ - struct _FbMqttPrivate - { - PurpleConnection *gc; -- GIOStream *conn; -- GBufferedInputStream *input; -- PurpleQueuedOutputStream *output; -- GCancellable *cancellable; -+ PurpleSslConnection *gsc; - gboolean connected; - guint16 mid; - - GByteArray *rbuf; -+ GByteArray *wbuf; - gsize remz; - - gint tev; -+ gint rev; -+ gint wev; - }; - - struct _FbMqttMessagePrivate -@@ -65,8 +64,6 @@ - G_DEFINE_TYPE(FbMqtt, fb_mqtt, G_TYPE_OBJECT); - G_DEFINE_TYPE(FbMqttMessage, fb_mqtt_message, G_TYPE_OBJECT); - --static void fb_mqtt_read_packet(FbMqtt *mqtt); -- - static void - fb_mqtt_dispose(GObject *obj) - { -@@ -75,6 +72,7 @@ - - fb_mqtt_close(mqtt); - g_byte_array_free(priv->rbuf, TRUE); -+ g_byte_array_free(priv->wbuf, TRUE); - } - - static void -@@ -158,6 +156,7 @@ - mqtt->priv = priv; - - priv->rbuf = g_byte_array_new(); -+ priv->wbuf = g_byte_array_new(); - } - - static void -@@ -201,6 +200,18 @@ - return q; - } - -+GQuark -+fb_mqtt_ssl_error_quark(void) -+{ -+ static GQuark q = 0; -+ -+ if (G_UNLIKELY(q == 0)) { -+ q = g_quark_from_static_string("fb-mqtt-ssl-error-quark"); -+ } -+ -+ return q; -+} -+ - FbMqtt * - fb_mqtt_new(PurpleConnection *gc) - { -@@ -224,47 +235,33 @@ - g_return_if_fail(FB_IS_MQTT(mqtt)); - priv = mqtt->priv; - -- if (priv->tev > 0) { -- g_source_remove(priv->tev); -- priv->tev = 0; -+ if (priv->wev > 0) { -+ purple_input_remove(priv->wev); -+ priv->wev = 0; - } - -- if (priv->cancellable != NULL) { -- g_cancellable_cancel(priv->cancellable); -- g_clear_object(&priv->cancellable); -+ if (priv->rev > 0) { -+ purple_input_remove(priv->rev); -+ priv->rev = 0; - } - -- if (priv->conn != NULL) { -- purple_gio_graceful_close(priv->conn, -- G_INPUT_STREAM(priv->input), -- G_OUTPUT_STREAM(priv->output)); -- g_clear_object(&priv->input); -- g_clear_object(&priv->output); -- g_clear_object(&priv->conn); -+ if (priv->tev > 0) { -+ purple_timeout_remove(priv->tev); -+ priv->tev = 0; - } - -- priv->connected = FALSE; -- g_byte_array_set_size(priv->rbuf, 0); --} -- --static void --fb_mqtt_take_error(FbMqtt *mqtt, GError *err, const gchar *prefix) --{ -- if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { -- /* Return as cancelled means the connection is closing */ -- g_error_free(err); -- return; -+ if (priv->gsc != NULL) { -+ purple_ssl_close(priv->gsc); -+ priv->gsc = NULL; - } - -- /* Now we can check for programming errors */ -- g_return_if_fail(FB_IS_MQTT(mqtt)); -- -- if (prefix != NULL) { -- g_prefix_error(&err, "%s: ", prefix); -+ if (priv->wbuf->len > 0) { -+ fb_util_debug_warning("Closing with unwritten data"); - } - -- g_signal_emit_by_name(mqtt, "error", err); -- g_error_free(err); -+ priv->connected = FALSE; -+ g_byte_array_set_size(priv->rbuf, 0); -+ g_byte_array_set_size(priv->wbuf, 0); - } - - void -@@ -342,127 +339,75 @@ - } - - static void --fb_mqtt_cb_fill(GObject *source, GAsyncResult *res, gpointer data) --{ -- GBufferedInputStream *input = G_BUFFERED_INPUT_STREAM(source); -- FbMqtt *mqtt = data; -- gssize ret; -- GError *err = NULL; -- -- ret = g_buffered_input_stream_fill_finish(input, res, &err); -- -- if (ret < 1) { -- if (ret == 0) { -- err = g_error_new_literal(G_IO_ERROR, -- G_IO_ERROR_CONNECTION_CLOSED, -- _("Connection closed")); -- } -- -- fb_mqtt_take_error(mqtt, err, _("Failed to read fixed header")); -- return; -- } -- -- fb_mqtt_read_packet(mqtt); --} -- --static void --fb_mqtt_cb_read_packet(GObject *source, GAsyncResult *res, gpointer data) -+fb_mqtt_cb_read(gpointer data, gint fd, PurpleInputCondition cond) - { - FbMqtt *mqtt = data; -- FbMqttPrivate *priv; -- gssize ret; - FbMqttMessage *msg; -- GError *err = NULL; -- -- ret = g_input_stream_read_finish(G_INPUT_STREAM(source), res, &err); -+ FbMqttPrivate *priv = mqtt->priv; -+ gint res; -+ guint mult; -+ guint8 buf[1024]; -+ guint8 byte; -+ gsize size; -+ gssize rize; - -- if (ret < 1) { -- if (ret == 0) { -- err = g_error_new_literal(G_IO_ERROR, -- G_IO_ERROR_CONNECTION_CLOSED, -- _("Connection closed")); -+ if (priv->remz < 1) { -+ /* Reset the read buffer */ -+ g_byte_array_set_size(priv->rbuf, 0); -+ -+ res = purple_ssl_read(priv->gsc, &byte, sizeof byte); -+ g_byte_array_append(priv->rbuf, &byte, sizeof byte); -+ -+ if (res != sizeof byte) { -+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, -+ _("Failed to read fixed header")); -+ return; - } - -- fb_mqtt_take_error(mqtt, err, _("Failed to read packet data")); -- return; -- } -+ mult = 1; - -- priv = mqtt->priv; -- priv->remz -= ret; -+ do { -+ res = purple_ssl_read(priv->gsc, &byte, sizeof byte); -+ g_byte_array_append(priv->rbuf, &byte, sizeof byte); -+ -+ if (res != sizeof byte) { -+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, -+ _("Failed to read packet size")); -+ return; -+ } - -- if (priv->remz > 0) { -- g_input_stream_read_async(G_INPUT_STREAM(source), -- priv->rbuf->data + -- priv->rbuf->len - priv->remz, priv->remz, -- G_PRIORITY_DEFAULT, priv->cancellable, -- fb_mqtt_cb_read_packet, mqtt); -- return; -+ priv->remz += (byte & 127) * mult; -+ mult *= 128; -+ } while ((byte & 128) != 0); - } - -- msg = fb_mqtt_message_new_bytes(priv->rbuf); -- -- if (G_UNLIKELY(msg == NULL)) { -- fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, -- _("Failed to parse message")); -- return; -- } -+ if (priv->remz > 0) { -+ size = MIN(priv->remz, sizeof buf); -+ rize = purple_ssl_read(priv->gsc, buf, size); - -- fb_mqtt_read(mqtt, msg); -- g_object_unref(msg); -+ if (rize < 1) { -+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, -+ _("Failed to read packet data")); -+ return; -+ } - -- /* Read another packet if connection wasn't reset in fb_mqtt_read() */ -- if (fb_mqtt_connected(mqtt, FALSE)) { -- fb_mqtt_read_packet(mqtt); -+ g_byte_array_append(priv->rbuf, buf, rize); -+ priv->remz -= rize; - } --} - --static void --fb_mqtt_read_packet(FbMqtt *mqtt) --{ -- FbMqttPrivate *priv = mqtt->priv; -- const guint8 const *buf; -- gsize count = 0; -- gsize pos; -- guint mult = 1; -- guint8 byte; -- gsize size = 0; -+ if (priv->remz < 1) { -+ msg = fb_mqtt_message_new_bytes(priv->rbuf); -+ priv->remz = 0; - -- buf = g_buffered_input_stream_peek_buffer(priv->input, &count); -- -- /* Start at 1 to skip the first byte */ -- pos = 1; -- -- do { -- if (pos >= count) { -- /* Not enough data yet, try again later */ -- g_buffered_input_stream_fill_async(priv->input, -1, -- G_PRIORITY_DEFAULT, priv->cancellable, -- fb_mqtt_cb_fill, mqtt); -+ if (G_UNLIKELY(msg == NULL)) { -+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, -+ _("Failed to parse message")); - return; - } - -- byte = *(buf + pos++); -- -- size += (byte & 127) * mult; -- mult *= 128; -- } while ((byte & 128) != 0); -- -- /* Add header to size */ -- size += pos; -- -- g_byte_array_set_size(priv->rbuf, size); -- priv->remz = size; -- -- /* TODO: Use g_input_stream_read_all_async() when available. */ -- /* TODO: Alternately, it would be nice to let the -- * FbMqttMessage directly use the GBufferedInputStream -- * buffer instead of copying it, provided it's consumed -- * before the next read. -- */ -- g_input_stream_read_async(G_INPUT_STREAM(priv->input), -- priv->rbuf->data, priv->rbuf->len, -- G_PRIORITY_DEFAULT, priv->cancellable, -- fb_mqtt_cb_read_packet, mqtt); -+ fb_mqtt_read(mqtt, msg); -+ g_object_unref(msg); -+ } - } - - void -@@ -569,16 +514,27 @@ - } - - static void --fb_mqtt_cb_flush(GObject *source, GAsyncResult *res, gpointer data) -+fb_mqtt_cb_write(gpointer data, gint fd, PurpleInputCondition cond) - { - FbMqtt *mqtt = data; -- GError *err = NULL; -+ FbMqttPrivate *priv = mqtt->priv; -+ gssize wize; - -- if (!g_output_stream_flush_finish(G_OUTPUT_STREAM(source), -- res, &err)) { -- fb_mqtt_take_error(mqtt, err, _("Failed to write data")); -+ wize = purple_ssl_write(priv->gsc, priv->wbuf->data, priv->wbuf->len); -+ -+ if (wize < 0) { -+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, -+ _("Failed to write data")); - return; - } -+ -+ if (wize > 0) { -+ g_byte_array_remove_range(priv->wbuf, 0, wize); -+ } -+ -+ if (priv->wbuf->len < 1) { -+ priv->wev = 0; -+ } - } - - void -@@ -587,7 +543,6 @@ - const GByteArray *bytes; - FbMqttMessagePrivate *mriv; - FbMqttPrivate *priv; -- GBytes *gbytes; - - g_return_if_fail(FB_IS_MQTT(mqtt)); - g_return_if_fail(FB_IS_MQTT_MESSAGE(msg)); -@@ -606,46 +561,46 @@ - "Writing %d (flags: 0x%0X)", - mriv->type, mriv->flags); - -- /* TODO: Would be nice to refactor this to not require copying bytes */ -- gbytes = g_bytes_new(bytes->data, bytes->len); -- purple_queued_output_stream_push_bytes(priv->output, gbytes); -- g_bytes_unref(gbytes); -+ g_byte_array_append(priv->wbuf, bytes->data, bytes->len); -+ fb_mqtt_cb_write(mqtt, priv->gsc->fd, PURPLE_INPUT_WRITE); - -- if (!g_output_stream_has_pending(G_OUTPUT_STREAM(priv->output))) { -- g_output_stream_flush_async(G_OUTPUT_STREAM(priv->output), -- G_PRIORITY_DEFAULT, priv->cancellable, -- fb_mqtt_cb_flush, mqtt); -+ if (priv->wev > 0) { -+ priv->wev = purple_input_add(priv->gsc->fd, -+ PURPLE_INPUT_WRITE, -+ fb_mqtt_cb_write, mqtt); - } - } - - static void --fb_mqtt_cb_open(GObject *source, GAsyncResult *res, gpointer data) -+fb_mqtt_cb_open(gpointer data, PurpleSslConnection *ssl, -+ PurpleInputCondition cond) - { - FbMqtt *mqtt = data; -- FbMqttPrivate *priv; -- GSocketConnection *conn; -- GError *err = NULL; -- -- conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source), -- res, &err); -- -- if (conn == NULL) { -- fb_mqtt_take_error(mqtt, err, NULL); -- return; -- } -+ FbMqttPrivate *priv = mqtt->priv; - - fb_mqtt_timeout_clear(mqtt); -+ priv->rev = purple_input_add(priv->gsc->fd, PURPLE_INPUT_READ, -+ fb_mqtt_cb_read, mqtt); -+ g_signal_emit_by_name(mqtt, "open"); -+} - -- priv = mqtt->priv; -- priv->conn = G_IO_STREAM(conn); -- priv->input = G_BUFFERED_INPUT_STREAM(g_buffered_input_stream_new( -- g_io_stream_get_input_stream(priv->conn))); -- priv->output = purple_queued_output_stream_new( -- g_io_stream_get_output_stream(priv->conn)); -+static void -+fb_mqtt_cb_open_error(PurpleSslConnection *ssl, PurpleSslErrorType error, -+ gpointer data) -+{ -+ const gchar *str; -+ FbMqtt *mqtt = data; -+ FbMqttPrivate *priv = mqtt->priv; -+ GError *err; - -- fb_mqtt_read_packet(mqtt); -+ str = purple_ssl_strerror(error); -+ err = g_error_new_literal(FB_MQTT_SSL_ERROR, error, str); - -- g_signal_emit_by_name(mqtt, "open"); -+ /* Do not call purple_ssl_close() from the error_func */ -+ priv->gsc = NULL; -+ -+ g_signal_emit_by_name(mqtt, "error", err); -+ g_error_free(err); - } - - void -@@ -653,29 +608,20 @@ - { - FbMqttPrivate *priv; - PurpleAccount *acc; -- GSocketClient *client; -- GError *err = NULL; - - g_return_if_fail(FB_IS_MQTT(mqtt)); - priv = mqtt->priv; - - acc = purple_connection_get_account(priv->gc); - fb_mqtt_close(mqtt); -+ priv->gsc = purple_ssl_connect(acc, host, port, fb_mqtt_cb_open, -+ fb_mqtt_cb_open_error, mqtt); - -- client = purple_gio_socket_client_new(acc, &err); -- -- if (client == NULL) { -- fb_mqtt_take_error(mqtt, err, NULL); -+ if (priv->gsc == NULL) { -+ fb_mqtt_cb_open_error(NULL, 0, mqtt); - return; - } - -- priv->cancellable = g_cancellable_new(); -- -- g_socket_client_set_tls(client, TRUE); -- g_socket_client_connect_to_host_async(client, host, port, -- priv->cancellable, fb_mqtt_cb_open, mqtt); -- g_object_unref(client); -- - fb_mqtt_timeout(mqtt); - } - -@@ -711,7 +657,7 @@ - - g_return_val_if_fail(FB_IS_MQTT(mqtt), FALSE); - priv = mqtt->priv; -- connected = (priv->conn != NULL) && priv->connected; -+ connected = (priv->gsc != NULL) && priv->connected; - - if (!connected && error) { - fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, -diff -r 56d191003b34 -r 5a63c26f21fd libpurple/protocols/facebook/mqtt.h ---- a/libpurple/protocols/facebook/mqtt.h Thu Sep 15 13:31:06 2016 -0500 -+++ b/libpurple/protocols/facebook/mqtt.h Wed Sep 14 14:53:12 2016 -0500 -@@ -107,6 +107,13 @@ - */ - #define FB_MQTT_ERROR fb_mqtt_error_quark() - -+/** -+ * FB_MQTT_SSL_ERROR: -+ * -+ * The #GQuark of the domain of MQTT SSL errors. -+ */ -+#define FB_MQTT_SSL_ERROR fb_mqtt_ssl_error_quark() -+ - typedef struct _FbMqtt FbMqtt; - typedef struct _FbMqttClass FbMqttClass; - typedef struct _FbMqttPrivate FbMqttPrivate; -@@ -291,6 +298,16 @@ - fb_mqtt_error_quark(void); - - /** -+ * fb_mqtt_ssl_error_quark: -+ * -+ * Gets the #GQuark of the domain of MQTT SSL errors. -+ * -+ * Returns: The #GQuark of the domain. -+ */ -+GQuark -+fb_mqtt_ssl_error_quark(void); -+ -+/** - * fb_mqtt_new: - * @gc: The #PurpleConnection. - * diff --git a/patches/05-revert-http-callbacks.patch b/patches/05-revert-http-callbacks.patch deleted file mode 100644 index c648a7dc..00000000 --- a/patches/05-revert-http-callbacks.patch +++ /dev/null @@ -1,42 +0,0 @@ -diff --git a/libpurple/http.c b/libpurple/http.c ---- a/libpurple/http.c -+++ b/libpurple/http.c -@@ -546,11 +546,7 @@ - cb_data = g_object_steal_data(source, "cb_data"); - - if (conn == NULL) { -- if (!g_error_matches(error, -- G_IO_ERROR, G_IO_ERROR_CANCELLED)) { -- cb(hs, error->message, cb_data); -- } -- -+ cb(hs, error->message, cb_data); - g_clear_error(&error); - return; - } -@@ -1223,10 +1219,8 @@ - &error); - got_anything = (len > 0); - -- if (len < 0 && (g_error_matches(error, -- G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) || -- g_error_matches(error, -- G_IO_ERROR, G_IO_ERROR_CANCELLED))) { -+ if (len < 0 && g_error_matches(error, -+ G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { - g_clear_error(&error); - return FALSE; - } -@@ -1511,10 +1505,8 @@ - &error); - } - -- if (written < 0 && (g_error_matches(error, -- G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) || -- g_error_matches(error, -- G_IO_ERROR, G_IO_ERROR_CANCELLED))) { -+ if (written < 0 && g_error_matches(error, -+ G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { - g_clear_error(&error); - return G_SOURCE_CONTINUE; - } diff --git a/patches/06-revert-purple-socket.patch b/patches/06-revert-purple-socket.patch deleted file mode 100644 index 230ccd09..00000000 --- a/patches/06-revert-purple-socket.patch +++ /dev/null @@ -1,637 +0,0 @@ -diff --git a/libpurple/purple-socket.c b/libpurple/purple-socket.c -new file mode 100644 ---- /dev/null -+++ b/libpurple/purple-socket.c -@@ -0,0 +1,410 @@ -+/* purple -+ * -+ * 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 "purple-socket.h" -+ -+#include "internal.h" -+ -+#include "debug.h" -+#include "proxy.h" -+#include "sslconn.h" -+ -+typedef enum { -+ PURPLE_SOCKET_STATE_DISCONNECTED = 0, -+ PURPLE_SOCKET_STATE_CONNECTING, -+ PURPLE_SOCKET_STATE_CONNECTED, -+ PURPLE_SOCKET_STATE_ERROR -+} PurpleSocketState; -+ -+struct _PurpleSocket -+{ -+ PurpleConnection *gc; -+ gchar *host; -+ int port; -+ gboolean is_tls; -+ GHashTable *data; -+ -+ PurpleSocketState state; -+ -+ PurpleSslConnection *tls_connection; -+ PurpleProxyConnectData *raw_connection; -+ int fd; -+ guint inpa; -+ -+ PurpleSocketConnectCb cb; -+ gpointer cb_data; -+}; -+ -+static GHashTable *handles = NULL; -+ -+static void -+handle_add(PurpleSocket *ps) -+{ -+ PurpleConnection *gc = ps->gc; -+ GSList *l; -+ -+ l = g_hash_table_lookup(handles, gc); -+ l = g_slist_prepend(l, ps); -+ g_hash_table_insert(handles, gc, l); -+} -+ -+static void -+handle_remove(PurpleSocket *ps) -+{ -+ PurpleConnection *gc = ps->gc; -+ GSList *l; -+ -+ l = g_hash_table_lookup(handles, gc); -+ l = g_slist_remove(l, ps); -+ g_hash_table_insert(handles, gc, l); -+} -+ -+void -+_purple_socket_init(void) -+{ -+ handles = g_hash_table_new(g_direct_hash, g_direct_equal); -+} -+ -+void -+_purple_socket_uninit(void) -+{ -+ g_hash_table_destroy(handles); -+ handles = NULL; -+} -+ -+PurpleSocket * -+purple_socket_new(PurpleConnection *gc) -+{ -+ PurpleSocket *ps = g_new0(PurpleSocket, 1); -+ -+ ps->gc = gc; -+ ps->fd = -1; -+ ps->port = -1; -+ ps->data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); -+ -+ handle_add(ps); -+ -+ return ps; -+} -+ -+PurpleConnection * -+purple_socket_get_connection(PurpleSocket *ps) -+{ -+ g_return_val_if_fail(ps != NULL, NULL); -+ -+ return ps->gc; -+} -+ -+static gboolean -+purple_socket_check_state(PurpleSocket *ps, PurpleSocketState wanted_state) -+{ -+ g_return_val_if_fail(ps != NULL, FALSE); -+ -+ if (ps->state == wanted_state) -+ return TRUE; -+ -+ purple_debug_error("socket", "invalid state: %d (should be: %d)", -+ ps->state, wanted_state); -+ ps->state = PURPLE_SOCKET_STATE_ERROR; -+ return FALSE; -+} -+ -+void -+purple_socket_set_tls(PurpleSocket *ps, gboolean is_tls) -+{ -+ g_return_if_fail(ps != NULL); -+ -+ if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) -+ return; -+ -+ ps->is_tls = is_tls; -+} -+ -+void -+purple_socket_set_host(PurpleSocket *ps, const gchar *host) -+{ -+ g_return_if_fail(ps != NULL); -+ -+ if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) -+ return; -+ -+ g_free(ps->host); -+ ps->host = g_strdup(host); -+} -+ -+void -+purple_socket_set_port(PurpleSocket *ps, int port) -+{ -+ g_return_if_fail(ps != NULL); -+ g_return_if_fail(port >= 0); -+ g_return_if_fail(port <= 65535); -+ -+ if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) -+ return; -+ -+ ps->port = port; -+} -+ -+static void -+_purple_socket_connected_raw(gpointer _ps, gint fd, const gchar *error_message) -+{ -+ PurpleSocket *ps = _ps; -+ -+ ps->raw_connection = NULL; -+ -+ if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTING)) { -+ if (fd > 0) -+ close(fd); -+ ps->cb(ps, _("Invalid socket state"), ps->cb_data); -+ return; -+ } -+ -+ if (fd <= 0 || error_message != NULL) { -+ if (error_message == NULL) -+ error_message = _("Unknown error"); -+ ps->fd = -1; -+ ps->state = PURPLE_SOCKET_STATE_ERROR; -+ ps->cb(ps, error_message, ps->cb_data); -+ return; -+ } -+ -+ ps->state = PURPLE_SOCKET_STATE_CONNECTED; -+ ps->fd = fd; -+ ps->cb(ps, NULL, ps->cb_data); -+} -+ -+static void -+_purple_socket_connected_tls(gpointer _ps, PurpleSslConnection *tls_connection, -+ PurpleInputCondition cond) -+{ -+ PurpleSocket *ps = _ps; -+ -+ if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTING)) { -+ purple_ssl_close(tls_connection); -+ ps->tls_connection = NULL; -+ ps->cb(ps, _("Invalid socket state"), ps->cb_data); -+ return; -+ } -+ -+ if (ps->tls_connection->fd <= 0) { -+ ps->state = PURPLE_SOCKET_STATE_ERROR; -+ purple_ssl_close(tls_connection); -+ ps->tls_connection = NULL; -+ ps->cb(ps, _("Invalid file descriptor"), ps->cb_data); -+ return; -+ } -+ -+ ps->state = PURPLE_SOCKET_STATE_CONNECTED; -+ ps->fd = ps->tls_connection->fd; -+ ps->cb(ps, NULL, ps->cb_data); -+} -+ -+static void -+_purple_socket_connected_tls_error(PurpleSslConnection *ssl_connection, -+ PurpleSslErrorType error, gpointer _ps) -+{ -+ PurpleSocket *ps = _ps; -+ -+ ps->state = PURPLE_SOCKET_STATE_ERROR; -+ ps->tls_connection = NULL; -+ ps->cb(ps, purple_ssl_strerror(error), ps->cb_data); -+} -+ -+gboolean -+purple_socket_connect(PurpleSocket *ps, PurpleSocketConnectCb cb, -+ gpointer user_data) -+{ -+ PurpleAccount *account = NULL; -+ -+ g_return_val_if_fail(ps != NULL, FALSE); -+ -+ if (ps->gc && purple_connection_is_disconnecting(ps->gc)) { -+ purple_debug_error("socket", "connection is being destroyed"); -+ ps->state = PURPLE_SOCKET_STATE_ERROR; -+ return FALSE; -+ } -+ -+ if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) -+ return FALSE; -+ ps->state = PURPLE_SOCKET_STATE_CONNECTING; -+ -+ if (ps->host == NULL || ps->port < 0) { -+ purple_debug_error("socket", "Host or port is not specified"); -+ ps->state = PURPLE_SOCKET_STATE_ERROR; -+ return FALSE; -+ } -+ -+ if (ps->gc != NULL) -+ account = purple_connection_get_account(ps->gc); -+ -+ ps->cb = cb; -+ ps->cb_data = user_data; -+ -+ if (ps->is_tls) { -+ ps->tls_connection = purple_ssl_connect(account, ps->host, -+ ps->port, _purple_socket_connected_tls, -+ _purple_socket_connected_tls_error, ps); -+ } else { -+ ps->raw_connection = purple_proxy_connect(ps->gc, account, -+ ps->host, ps->port, _purple_socket_connected_raw, ps); -+ } -+ -+ if (ps->tls_connection == NULL && -+ ps->raw_connection == NULL) -+ { -+ ps->state = PURPLE_SOCKET_STATE_ERROR; -+ return FALSE; -+ } -+ -+ return TRUE; -+} -+ -+gssize -+purple_socket_read(PurpleSocket *ps, guchar *buf, size_t len) -+{ -+ g_return_val_if_fail(ps != NULL, -1); -+ g_return_val_if_fail(buf != NULL, -1); -+ -+ if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) -+ return -1; -+ -+ if (ps->is_tls) -+ return purple_ssl_read(ps->tls_connection, buf, len); -+ else -+ return read(ps->fd, buf, len); -+} -+ -+gssize -+purple_socket_write(PurpleSocket *ps, const guchar *buf, size_t len) -+{ -+ g_return_val_if_fail(ps != NULL, -1); -+ g_return_val_if_fail(buf != NULL, -1); -+ -+ if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) -+ return -1; -+ -+ if (ps->is_tls) -+ return purple_ssl_write(ps->tls_connection, buf, len); -+ else -+ return write(ps->fd, buf, len); -+} -+ -+void -+purple_socket_watch(PurpleSocket *ps, PurpleInputCondition cond, -+ PurpleInputFunction func, gpointer user_data) -+{ -+ g_return_if_fail(ps != NULL); -+ -+ if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) -+ return; -+ -+ if (ps->inpa > 0) -+ purple_input_remove(ps->inpa); -+ ps->inpa = 0; -+ -+ g_return_if_fail(ps->fd > 0); -+ -+ if (func != NULL) -+ ps->inpa = purple_input_add(ps->fd, cond, func, user_data); -+} -+ -+int -+purple_socket_get_fd(PurpleSocket *ps) -+{ -+ g_return_val_if_fail(ps != NULL, -1); -+ -+ if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) -+ return -1; -+ -+ g_return_val_if_fail(ps->fd > 0, -1); -+ -+ return ps->fd; -+} -+ -+void -+purple_socket_set_data(PurpleSocket *ps, const gchar *key, gpointer data) -+{ -+ g_return_if_fail(ps != NULL); -+ g_return_if_fail(key != NULL); -+ -+ if (data == NULL) -+ g_hash_table_remove(ps->data, key); -+ else -+ g_hash_table_insert(ps->data, g_strdup(key), data); -+} -+ -+gpointer -+purple_socket_get_data(PurpleSocket *ps, const gchar *key) -+{ -+ g_return_val_if_fail(ps != NULL, NULL); -+ g_return_val_if_fail(key != NULL, NULL); -+ -+ return g_hash_table_lookup(ps->data, key); -+} -+ -+static void -+purple_socket_cancel(PurpleSocket *ps) -+{ -+ if (ps->inpa > 0) -+ purple_input_remove(ps->inpa); -+ ps->inpa = 0; -+ -+ if (ps->tls_connection != NULL) { -+ purple_ssl_close(ps->tls_connection); -+ ps->fd = -1; -+ } -+ ps->tls_connection = NULL; -+ -+ if (ps->raw_connection != NULL) -+ purple_proxy_connect_cancel(ps->raw_connection); -+ ps->raw_connection = NULL; -+ -+ if (ps->fd > 0) -+ close(ps->fd); -+ ps->fd = 0; -+} -+ -+void -+purple_socket_destroy(PurpleSocket *ps) -+{ -+ if (ps == NULL) -+ return; -+ -+ handle_remove(ps); -+ -+ purple_socket_cancel(ps); -+ -+ g_free(ps->host); -+ g_hash_table_destroy(ps->data); -+ g_free(ps); -+} -+ -+void -+_purple_socket_cancel_with_connection(PurpleConnection *gc) -+{ -+ GSList *it; -+ -+ it = g_hash_table_lookup(handles, gc); -+ for (; it; it = g_slist_next(it)) { -+ PurpleSocket *ps = it->data; -+ purple_socket_cancel(ps); -+ } -+} -diff --git a/libpurple/purple-socket.h b/libpurple/purple-socket.h -new file mode 100644 ---- /dev/null -+++ b/libpurple/purple-socket.h -@@ -0,0 +1,217 @@ -+/* purple -+ * -+ * 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 -+ */ -+ -+#ifndef _PURPLE_SOCKET_H_ -+#define _PURPLE_SOCKET_H_ -+/** -+ * SECTION:purple-socket -+ * @section_id: libpurple-purple-socket -+ * @short_description: purple-socket.h -+ * @title: Generic Sockets -+ */ -+ -+#include "connection.h" -+ -+/** -+ * PurpleSocket: -+ * -+ * A structure holding all resources needed for the TCP connection. -+ */ -+typedef struct _PurpleSocket PurpleSocket; -+ -+/** -+ * PurpleSocketConnectCb: -+ * @ps: The socket. -+ * @error: Error message, or NULL if connection was successful. -+ * @user_data: The user data passed with callback function. -+ * -+ * A callback fired after (successfully or not) establishing a connection. -+ */ -+typedef void (*PurpleSocketConnectCb)(PurpleSocket *ps, const gchar *error, -+ gpointer user_data); -+ -+/** -+ * purple_socket_new: -+ * @gc: The connection for which the socket is needed, or NULL. -+ * -+ * Creates new, disconnected socket. -+ * -+ * Passing a PurpleConnection allows for proper proxy handling. -+ * -+ * Returns: The new socket struct. -+ */ -+PurpleSocket * -+purple_socket_new(PurpleConnection *gc); -+ -+/** -+ * purple_socket_get_connection: -+ * @ps: The socket. -+ * -+ * Gets PurpleConnection tied with specified socket. -+ * -+ * Returns: The PurpleConnection object. -+ */ -+PurpleConnection * -+purple_socket_get_connection(PurpleSocket *ps); -+ -+/** -+ * purple_socket_set_tls: -+ * @ps: The socket. -+ * @is_tls: TRUE, if TLS should be handled transparently, FALSE otherwise. -+ * -+ * Determines, if socket should handle TLS. -+ */ -+void -+purple_socket_set_tls(PurpleSocket *ps, gboolean is_tls); -+ -+/** -+ * purple_socket_set_host: -+ * @ps: The socket. -+ * @host: The connection host. -+ * -+ * Sets connection host. -+ */ -+void -+purple_socket_set_host(PurpleSocket *ps, const gchar *host); -+ -+/** -+ * purple_socket_set_port: -+ * @ps: The socket. -+ * @port: The connection port. -+ * -+ * Sets connection port. -+ */ -+void -+purple_socket_set_port(PurpleSocket *ps, int port); -+ -+/** -+ * purple_socket_connect: -+ * @ps: The socket. -+ * @cb: The function to call after establishing a connection, or on -+ * error. -+ * @user_data: The user data to be passed to callback function. -+ * -+ * Establishes a connection. -+ * -+ * Returns: TRUE on success (this doesn't mean it's connected yet), FALSE -+ * otherwise. -+ */ -+gboolean -+purple_socket_connect(PurpleSocket *ps, PurpleSocketConnectCb cb, -+ gpointer user_data); -+ -+/** -+ * purple_socket_read: -+ * @ps: The socket. -+ * @buf: The buffer to write data to. -+ * @len: The buffer size. -+ * -+ * Reads incoming data from socket. -+ * -+ * This function deals with TLS, if the socket is configured to do it. -+ * -+ * Returns: Amount of data written, or -1 on error (errno will be also be set). -+ */ -+gssize -+purple_socket_read(PurpleSocket *ps, guchar *buf, size_t len); -+ -+/** -+ * purple_socket_write: -+ * @ps: The socket. -+ * @buf: The buffer to read data from. -+ * @len: The amount of data to read and send. -+ * -+ * Sends data through socket. -+ * -+ * This function deals with TLS, if the socket is configured to do it. -+ * -+ * Returns: Amount of data sent, or -1 on error (errno will albo be set). -+ */ -+gssize -+purple_socket_write(PurpleSocket *ps, const guchar *buf, size_t len); -+ -+/** -+ * purple_socket_watch: -+ * @ps: The socket. -+ * @cond: The condition type. -+ * @func: The callback function for data, or NULL to remove any -+ * existing callbacks. -+ * @user_data: The user data to be passed to callback function. -+ * -+ * Adds an input handler for the socket. -+ * -+ * If the specified socket had input handler already registered, it will be -+ * removed. To remove any input handlers, pass an NULL handler function. -+ */ -+void -+purple_socket_watch(PurpleSocket *ps, PurpleInputCondition cond, -+ PurpleInputFunction func, gpointer user_data); -+ -+/** -+ * purple_socket_get_fd: -+ * @ps: The socket -+ * -+ * Gets underlying file descriptor for socket. -+ * -+ * It's not meant to read/write data (use purple_socket_read/ -+ * purple_socket_write), rather for watching for changes with select(). -+ * -+ * Returns: The file descriptor, or -1 on error. -+ */ -+int -+purple_socket_get_fd(PurpleSocket *ps); -+ -+/** -+ * purple_socket_set_data: -+ * @ps: The socket. -+ * @key: The unique key. -+ * @data: The data to assign, or NULL to remove. -+ * -+ * Sets extra data for a socket. -+ */ -+void -+purple_socket_set_data(PurpleSocket *ps, const gchar *key, gpointer data); -+ -+/** -+ * purple_socket_get_data: -+ * @ps: The socket. -+ * @key: The unqiue key. -+ * -+ * Returns extra data in a socket. -+ * -+ * Returns: The data associated with the key. -+ */ -+gpointer -+purple_socket_get_data(PurpleSocket *ps, const gchar *key); -+ -+/** -+ * purple_socket_destroy: -+ * @ps: The socket. -+ * -+ * Destroys the socket, closes connection and frees all resources. -+ * -+ * If file descriptor for the socket was extracted with purple_socket_get_fd and -+ * added to event loop, it have to be removed prior this. -+ */ -+void -+purple_socket_destroy(PurpleSocket *ps); -+ -+#endif /* _PURPLE_SOCKET_H_ */ diff --git a/patches/07-revert-http-gio.patch b/patches/07-revert-http-gio.patch deleted file mode 100644 index 58370c5d..00000000 --- a/patches/07-revert-http-gio.patch +++ /dev/null @@ -1,428 +0,0 @@ -diff --git a/libpurple/http.c b/libpurple/http.c ---- a/libpurple/http.c -+++ b/libpurple/http.c -@@ -27,7 +27,7 @@ - - #include "debug.h" - #include "proxy.h" --#include "purple-gio.h" -+#include "purple-socket.h" - - #define PURPLE_HTTP_URL_CREDENTIALS_CHARS "a-z0-9.,~_/*!&%?=+\\^-" - #define PURPLE_HTTP_MAX_RECV_BUFFER_LEN 10240 -@@ -51,15 +51,9 @@ - - typedef struct _PurpleHttpGzStream PurpleHttpGzStream; - --typedef void (*PurpleHttpSocketConnectCb)(PurpleHttpSocket *hs, -- const gchar *error, gpointer _hc); -- - struct _PurpleHttpSocket - { -- GSocketConnection *conn; -- GCancellable *cancellable; -- guint input_source; -- guint output_source; -+ PurpleSocket *ps; - - gboolean is_busy; - guint use_count; -@@ -175,7 +169,7 @@ - struct _PurpleHttpKeepaliveRequest - { - PurpleConnection *gc; -- PurpleHttpSocketConnectCb cb; -+ PurpleSocketConnectCb cb; - gpointer user_data; - - PurpleHttpKeepaliveHost *host; -@@ -266,7 +260,7 @@ - static PurpleHttpKeepaliveRequest * - purple_http_keepalive_pool_request(PurpleHttpKeepalivePool *pool, - PurpleConnection *gc, const gchar *host, int port, gboolean is_ssl, -- PurpleHttpSocketConnectCb cb, gpointer user_data); -+ PurpleSocketConnectCb cb, gpointer user_data); - static void - purple_http_keepalive_pool_request_cancel(PurpleHttpKeepaliveRequest *req); - static void -@@ -529,65 +523,23 @@ - return g_strdup_printf("%c:%s:%d", (is_ssl ? 'S' : 'R'), host, port); - } - --static void --purple_http_socket_connect_new_cb(GObject *source, GAsyncResult *res, -- gpointer user_data) --{ -- PurpleHttpSocket *hs = user_data; -- GSocketConnection *conn; -- PurpleHttpSocketConnectCb cb; -- gpointer cb_data; -- GError *error = NULL; -- -- conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source), -- res, &error); -- -- cb = g_object_steal_data(source, "cb"); -- cb_data = g_object_steal_data(source, "cb_data"); -- -- if (conn == NULL) { -- cb(hs, error->message, cb_data); -- g_clear_error(&error); -- return; -- } -- -- hs->conn = conn; -- -- cb(hs, NULL, cb_data); --} -- - static PurpleHttpSocket * - purple_http_socket_connect_new(PurpleConnection *gc, const gchar *host, -- int port, gboolean is_ssl, -- PurpleHttpSocketConnectCb cb, gpointer user_data) -+ int port, gboolean is_ssl, PurpleSocketConnectCb cb, gpointer user_data) - { -- PurpleHttpSocket *hs; -- GSocketClient *client; -- GError *error = NULL; -- -- client = purple_gio_socket_client_new( -- purple_connection_get_account(gc), &error); -+ PurpleHttpSocket *hs = g_new0(PurpleHttpSocket, 1); - -- if (client == NULL) { -- purple_debug_error("http", "Error connecting to '%s:%d': %s", -- host, port, error->message); -- g_clear_error(&error); -+ hs->ps = purple_socket_new(gc); -+ purple_socket_set_data(hs->ps, "hs", hs); -+ purple_socket_set_tls(hs->ps, is_ssl); -+ purple_socket_set_host(hs->ps, host); -+ purple_socket_set_port(hs->ps, port); -+ if (!purple_socket_connect(hs->ps, cb, user_data)) { -+ purple_socket_destroy(hs->ps); -+ g_free(hs); - return NULL; - } - -- hs = g_new0(PurpleHttpSocket, 1); -- hs->cancellable = g_cancellable_new(); -- -- g_socket_client_set_tls(client, is_ssl); -- g_object_set_data(G_OBJECT(client), "cb", cb); -- g_object_set_data(G_OBJECT(client), "cb_data", user_data); -- -- g_socket_client_connect_to_host_async(client, -- host, port, hs->cancellable, -- purple_http_socket_connect_new_cb, hs); -- -- g_object_unref(client); -- - if (purple_debug_is_verbose()) - purple_debug_misc("http", "new socket created: %p\n", hs); - -@@ -603,26 +555,7 @@ - if (purple_debug_is_verbose()) - purple_debug_misc("http", "destroying socket: %p\n", hs); - -- if (hs->input_source > 0) { -- g_source_remove(hs->input_source); -- hs->input_source = 0; -- } -- -- if (hs->output_source > 0) { -- g_source_remove(hs->output_source); -- hs->output_source = 0; -- } -- -- if (hs->cancellable != NULL) { -- g_cancellable_cancel(hs->cancellable); -- g_clear_object(&hs->cancellable); -- } -- -- if (hs->conn != NULL) { -- purple_gio_graceful_close(G_IO_STREAM(hs->conn), NULL, NULL); -- g_clear_object(&hs->conn); -- } -- -+ purple_socket_destroy(hs->ps); - g_free(hs); - } - -@@ -809,9 +742,10 @@ - gboolean is_graceful); - - static void _purple_http_gen_headers(PurpleHttpConnection *hc); --static gboolean _purple_http_recv_loopbody(PurpleHttpConnection *hc); --static gboolean _purple_http_recv(GObject *source, gpointer _hc); --static gboolean _purple_http_send(GObject *source, gpointer _hc); -+static gboolean _purple_http_recv_loopbody(PurpleHttpConnection *hc, gint fd); -+static void _purple_http_recv(gpointer _hc, gint fd, -+ PurpleInputCondition cond); -+static void _purple_http_send(gpointer _hc, gint fd, PurpleInputCondition cond); - - /* closes current connection (if exists), estabilishes one and proceeds with - * request */ -@@ -1204,31 +1138,21 @@ - return _purple_http_recv_body_data(hc, buf, len); - } - --static gboolean _purple_http_recv_loopbody(PurpleHttpConnection *hc) -+static gboolean _purple_http_recv_loopbody(PurpleHttpConnection *hc, gint fd) - { - int len; - gchar buf[4096]; - gboolean got_anything; -- GError *error = NULL; - -- len = g_pollable_input_stream_read_nonblocking( -- G_POLLABLE_INPUT_STREAM( -- g_io_stream_get_input_stream( -- G_IO_STREAM(hc->socket->conn))), -- buf, sizeof(buf), hc->socket->cancellable, -- &error); -+ len = purple_socket_read(hc->socket->ps, (guchar*)buf, sizeof(buf)); - got_anything = (len > 0); - -- if (len < 0 && g_error_matches(error, -- G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { -- g_clear_error(&error); -+ if (len < 0 && errno == EAGAIN) - return FALSE; -- } - - if (len < 0) { - _purple_http_error(hc, _("Error reading from %s: %s"), -- hc->url->host, error->message); -- g_clear_error(&error); -+ hc->url->host, g_strerror(errno)); - return FALSE; - } - -@@ -1407,13 +1331,11 @@ - return got_anything; - } - --static gboolean _purple_http_recv(GObject *source, gpointer _hc) -+static void _purple_http_recv(gpointer _hc, gint fd, PurpleInputCondition cond) - { - PurpleHttpConnection *hc = _hc; - -- while (_purple_http_recv_loopbody(hc)); -- -- return G_SOURCE_CONTINUE; -+ while (_purple_http_recv_loopbody(hc, fd)); - } - - static void _purple_http_send_got_data(PurpleHttpConnection *hc, -@@ -1444,19 +1366,17 @@ - hc->request->contents_length = estimated_length; - } - --static gboolean _purple_http_send(GObject *source, gpointer _hc) -+static void _purple_http_send(gpointer _hc, gint fd, PurpleInputCondition cond) - { - PurpleHttpConnection *hc = _hc; - int written, write_len; - const gchar *write_from; - gboolean writing_headers; -- GError *error = NULL; -- GSource *gsource; - - /* Waiting for data. This could be written more efficiently, by removing - * (and later, adding) hs->inpa. */ - if (hc->contents_reader_requested) -- return G_SOURCE_CONTINUE; -+ return; - - _purple_http_gen_headers(hc); - -@@ -1469,7 +1389,7 @@ - hc->request_header_written; - } else if (hc->request->contents_reader) { - if (hc->contents_reader_requested) -- return G_SOURCE_CONTINUE; /* waiting for data */ -+ return; /* waiting for data */ - if (!hc->contents_reader_buffer) - hc->contents_reader_buffer = g_string_new(""); - if (hc->contents_reader_buffer->len == 0) { -@@ -1482,7 +1402,7 @@ - PURPLE_HTTP_MAX_READ_BUFFER_LEN, - hc->request->contents_reader_data, - _purple_http_send_got_data); -- return G_SOURCE_CONTINUE; -+ return; - } - write_from = hc->contents_reader_buffer->str; - write_len = hc->contents_reader_buffer->len; -@@ -1497,19 +1417,12 @@ - purple_debug_warning("http", "Nothing to write\n"); - written = 0; - } else { -- written = g_pollable_output_stream_write_nonblocking( -- G_POLLABLE_OUTPUT_STREAM( -- g_io_stream_get_output_stream( -- G_IO_STREAM(hc->socket->conn))), -- write_from, write_len, hc->socket->cancellable, -- &error); -+ written = purple_socket_write(hc->socket->ps, -+ (const guchar*)write_from, write_len); - } - -- if (written < 0 && g_error_matches(error, -- G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { -- g_clear_error(&error); -- return G_SOURCE_CONTINUE; -- } -+ if (written < 0 && errno == EAGAIN) -+ return; - - if (written < 0) { - if (hc->request_header_written == 0 && -@@ -1518,22 +1431,21 @@ - purple_debug_info("http", "Keep-alive connection " - "expired (when writing), retrying...\n"); - purple_http_conn_retry(hc); -- } else { -- _purple_http_error(hc, _("Error writing to %s: %s"), -- hc->url->host, error->message); -+ return; - } - -- g_clear_error(&error); -- return G_SOURCE_CONTINUE; -+ _purple_http_error(hc, _("Error writing to %s: %s"), -+ hc->url->host, g_strerror(errno)); -+ return; - } - - if (writing_headers) { - hc->request_header_written += written; - purple_http_conn_notify_progress_watcher(hc); - if (hc->request_header_written < hc->request_header->len) -- return G_SOURCE_CONTINUE; -+ return; - if (hc->request->contents_length > 0) -- return G_SOURCE_CONTINUE; -+ return; - } else { - hc->request_contents_written += written; - purple_http_conn_notify_progress_watcher(hc); -@@ -1543,24 +1455,14 @@ - hc->request_contents_written < - (guint)hc->request->contents_length) - { -- return G_SOURCE_CONTINUE; -+ return; - } - } - - /* request is completely written, let's read the response */ - hc->is_reading = TRUE; -- gsource = g_pollable_input_stream_create_source( -- G_POLLABLE_INPUT_STREAM( -- g_io_stream_get_input_stream( -- G_IO_STREAM(hc->socket->conn))), -- NULL); -- g_source_set_callback(gsource, -- (GSourceFunc)_purple_http_recv, hc, NULL); -- hc->socket->input_source = g_source_attach(gsource, NULL); -- g_source_unref(gsource); -- -- hc->socket->output_source = 0; -- return G_SOURCE_REMOVE; -+ purple_socket_watch(hc->socket->ps, PURPLE_INPUT_READ, -+ _purple_http_recv, hc); - } - - static void _purple_http_disconnect(PurpleHttpConnection *hc, -@@ -1585,10 +1487,13 @@ - } - - static void --_purple_http_connected(PurpleHttpSocket *hs, const gchar *error, gpointer _hc) -+_purple_http_connected(PurpleSocket *ps, const gchar *error, gpointer _hc) - { -+ PurpleHttpSocket *hs = NULL; - PurpleHttpConnection *hc = _hc; -- GSource *source; -+ -+ if (ps != NULL) -+ hs = purple_socket_get_data(ps, "hs"); - - hc->socket_request = NULL; - hc->socket = hs; -@@ -1599,14 +1504,7 @@ - return; - } - -- source = g_pollable_output_stream_create_source( -- G_POLLABLE_OUTPUT_STREAM( -- g_io_stream_get_output_stream(G_IO_STREAM(hs->conn))), -- NULL); -- g_source_set_callback(source, -- (GSourceFunc)_purple_http_send, hc, NULL); -- hc->socket->output_source = g_source_attach(source, NULL); -- g_source_unref(source); -+ purple_socket_watch(ps, PURPLE_INPUT_WRITE, _purple_http_send, hc); - } - - static gboolean _purple_http_reconnect(PurpleHttpConnection *hc) -@@ -2318,7 +2216,7 @@ - static PurpleHttpKeepaliveRequest * - purple_http_keepalive_pool_request(PurpleHttpKeepalivePool *pool, - PurpleConnection *gc, const gchar *host, int port, gboolean is_ssl, -- PurpleHttpSocketConnectCb cb, gpointer user_data) -+ PurpleSocketConnectCb cb, gpointer user_data) - { - PurpleHttpKeepaliveRequest *req; - PurpleHttpKeepaliveHost *kahost; -@@ -2361,15 +2259,19 @@ - } - - static void --_purple_http_keepalive_socket_connected(PurpleHttpSocket *hs, -+_purple_http_keepalive_socket_connected(PurpleSocket *ps, - const gchar *error, gpointer _req) - { -+ PurpleHttpSocket *hs = NULL; - PurpleHttpKeepaliveRequest *req = _req; - -+ if (ps != NULL) -+ hs = purple_socket_get_data(ps, "hs"); -+ - if (hs != NULL) - hs->use_count++; - -- req->cb(hs, error, req->user_data); -+ req->cb(ps, error, req->user_data); - g_free(req); - } - -@@ -2425,7 +2327,7 @@ - - purple_http_keepalive_host_process_queue(host); - -- req->cb(hs, NULL, req->user_data); -+ req->cb(hs->ps, NULL, req->user_data); - g_free(req); - - return FALSE; -@@ -2496,16 +2398,7 @@ - if (purple_debug_is_verbose()) - purple_debug_misc("http", "releasing a socket: %p\n", hs); - -- if (hs->input_source > 0) { -- g_source_remove(hs->input_source); -- hs->input_source = 0; -- } -- -- if (hs->output_source > 0) { -- g_source_remove(hs->output_source); -- hs->output_source = 0; -- } -- -+ purple_socket_watch(hs->ps, 0, NULL, NULL); - hs->is_busy = FALSE; - host = hs->host; - diff --git a/patches/08-revert-marshaller.patch b/patches/08-revert-marshaller.patch deleted file mode 100644 index d902a80a..00000000 --- a/patches/08-revert-marshaller.patch +++ /dev/null @@ -1,367 +0,0 @@ -diff --git a/libpurple/protocols/facebook/Makefile.am b/libpurple/protocols/facebook/Makefile.am ---- a/libpurple/protocols/facebook/Makefile.am -+++ b/libpurple/protocols/facebook/Makefile.am -@@ -1,11 +1,14 @@ - EXTRA_DIST = \ -- Makefile.mingw -+ Makefile.mingw \ -+ marshaller.list - - pkgdir = @PURPLE_PLUGINDIR@ - - FACEBOOKSOURCES = \ -+ marshal.c \ -+ marshal.h \ - api.c \ - api.h \ - data.c \ - data.h \ - facebook.h \ -@@ -25,10 +28,21 @@ - ../../http.c \ - ../../http.h \ - ../../purple-socket.h \ - ../../purple-socket.c - -+CLEANFILES = \ -+ marshal.c \ -+ marshal.h -+ -+marshal.c: $(srcdir)/marshaller.list marshal.h -+ $(AM_V_GEN)echo "#include \"marshal.h\"" > $@ -+ $(AM_V_at)$(GLIB_GENMARSHAL) --prefix=fb_marshal --body $(srcdir)/marshaller.list >> $@ -+ -+marshal.h: $(srcdir)/marshaller.list -+ $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=fb_marshal --header $(srcdir)/marshaller.list > $@ -+ - AM_CFLAGS = $(st) - - libfacebook_la_LDFLAGS = -module @PLUGIN_LDFLAGS@ - - if STATIC_FACEBOOK -diff --git a/libpurple/protocols/facebook/Makefile.mingw b/libpurple/protocols/facebook/Makefile.mingw ---- a/libpurple/protocols/facebook/Makefile.mingw -+++ b/libpurple/protocols/facebook/Makefile.mingw -@@ -42,6 +42,7 @@ - ## SOURCES, OBJECTS - ## - C_SRC = \ -+ marshal.c \ - api.c \ - data.c \ - facebook.c \ -@@ -85,11 +86,19 @@ - $(TARGET).dll: $(OBJECTS) - $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll - -+marshal.c: marshaller.list marshal.h -+ @echo "#include \"marshal.h\"" > $@ -+ @$(GLIB_GENMARSHAL) --prefix=fb_marshal --body marshaller.list >> $@ -+ -+marshal.h: marshaller.list -+ @$(GLIB_GENMARSHAL) --prefix=fb_marshal --header marshaller.list > $@ -+ -+ - ## - ## CLEAN RULES - ## - clean: -- rm -f $(OBJECTS) -+ rm -f $(OBJECTS) marshal.c marshal.h - rm -f $(TARGET).dll - - include $(PIDGIN_COMMON_TARGETS) -diff --git a/libpurple/protocols/facebook/api.c b/libpurple/protocols/facebook/api.c ---- a/libpurple/protocols/facebook/api.c -+++ b/libpurple/protocols/facebook/api.c -@@ -26,10 +26,11 @@ - #include "glibcompat.h" - - #include "api.h" - #include "http.h" - #include "json.h" -+#include "marshal.h" - #include "thrift.h" - #include "util.h" - - typedef struct _FbApiData FbApiData; - -@@ -289,11 +290,12 @@ - */ - g_signal_new("auth", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__VOID, - G_TYPE_NONE, - 0); - - /** - * FbApi::connect: -@@ -304,11 +306,12 @@ - */ - g_signal_new("connect", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__VOID, - G_TYPE_NONE, - 0); - - /** - * FbApi::contact: -@@ -320,11 +323,12 @@ - */ - g_signal_new("contact", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::contacts: -@@ -340,11 +344,12 @@ - */ - g_signal_new("contacts", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__POINTER_BOOLEAN, - G_TYPE_NONE, - 2, G_TYPE_POINTER, G_TYPE_BOOLEAN); - - /** - * FbApi::contacts-delta: -@@ -356,11 +361,12 @@ - */ - g_signal_new("contacts-delta", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__POINTER_POINTER, - G_TYPE_NONE, - 2, G_TYPE_POINTER, G_TYPE_POINTER); - - /** - * FbApi::error: -@@ -372,11 +378,12 @@ - */ - g_signal_new("error", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__OBJECT, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::events: -@@ -387,11 +394,12 @@ - */ - g_signal_new("events", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::messages: -@@ -402,11 +410,12 @@ - */ - g_signal_new("messages", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::presences: -@@ -417,11 +426,12 @@ - */ - g_signal_new("presences", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::thread: -@@ -433,11 +443,12 @@ - */ - g_signal_new("thread", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::thread-create: -@@ -450,11 +461,12 @@ - */ - g_signal_new("thread-create", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__INT64, - G_TYPE_NONE, - 1, FB_TYPE_ID); - - /** - * FbApi::thread-kicked: -@@ -466,11 +478,12 @@ - */ - g_signal_new("thread-kicked", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::threads: -@@ -482,11 +495,12 @@ - */ - g_signal_new("threads", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::typing: -@@ -497,11 +511,12 @@ - */ - g_signal_new("typing", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - } - - static void -diff --git a/libpurple/protocols/facebook/marshaller.list b/libpurple/protocols/facebook/marshaller.list -new file mode 100644 ---- /dev/null -+++ b/libpurple/protocols/facebook/marshaller.list -@@ -0,0 +1,7 @@ -+VOID:INT64 -+VOID:OBJECT -+VOID:POINTER -+VOID:POINTER,BOOLEAN -+VOID:STRING,BOXED -+VOID:VOID -+VOID:POINTER,POINTER -diff --git a/libpurple/protocols/facebook/mqtt.c b/libpurple/protocols/facebook/mqtt.c ---- a/libpurple/protocols/facebook/mqtt.c -+++ b/libpurple/protocols/facebook/mqtt.c -@@ -28,10 +28,11 @@ - #include "account.h" - #include "eventloop.h" - #include "glibcompat.h" - #include "sslconn.h" - -+#include "marshal.h" - #include "mqtt.h" - #include "util.h" - - struct _FbMqttPrivate - { -@@ -92,11 +93,12 @@ - */ - g_signal_new("connect", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__VOID, - G_TYPE_NONE, - 0); - - /** - * FbMqtt::error: -@@ -108,11 +110,12 @@ - */ - g_signal_new("error", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__OBJECT, - G_TYPE_NONE, - 1, G_TYPE_ERROR); - - /** - * FbMqtt::open: -@@ -124,11 +127,12 @@ - */ - g_signal_new("open", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__VOID, - G_TYPE_NONE, - 0); - - /** - * FbMqtt::publish: -@@ -140,11 +144,12 @@ - */ - g_signal_new("publish", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, -- NULL, NULL, NULL, -+ NULL, NULL, -+ fb_marshal_VOID__STRING_BOXED, - G_TYPE_NONE, - 2, G_TYPE_STRING, G_TYPE_BYTE_ARRAY); - } - - static void diff --git a/patches/09-revert-http-zlib.patch b/patches/09-revert-http-zlib.patch deleted file mode 100644 index 90e49fd6..00000000 --- a/patches/09-revert-http-zlib.patch +++ /dev/null @@ -1,148 +0,0 @@ -diff -Naur a/libpurple/http.c b/libpurple/http.c ---- a/libpurple/http.c -+++ b/libpurple/http.c -@@ -29,6 +29,11 @@ - #include "proxy.h" - #include "purple-socket.h" - -+#include -+#ifndef z_const -+#define z_const -+#endif -+ - #define PURPLE_HTTP_URL_CREDENTIALS_CHARS "a-z0-9.,~_/*!&%?=+\\^-" - #define PURPLE_HTTP_MAX_RECV_BUFFER_LEN 10240 - #define PURPLE_HTTP_MAX_READ_BUFFER_LEN 10240 -@@ -212,7 +217,7 @@ - struct _PurpleHttpGzStream - { - gboolean failed; -- GZlibDecompressor *decompressor; -+ z_stream zs; - gsize max_output; - gsize decompressed; - GString *pending; -@@ -360,14 +365,19 @@ - purple_http_gz_new(gsize max_output, gboolean is_deflate) - { - PurpleHttpGzStream *gzs = g_new0(PurpleHttpGzStream, 1); -- GZlibCompressorFormat format; -+ int windowBits; - - if (is_deflate) -- format = G_ZLIB_COMPRESSOR_FORMAT_RAW; -+ windowBits = -MAX_WBITS; - else /* is gzip */ -- format = G_ZLIB_COMPRESSOR_FORMAT_GZIP; -+ windowBits = MAX_WBITS + 32; -+ -+ if (inflateInit2(&gzs->zs, windowBits) != Z_OK) { -+ purple_debug_error("http", "Cannot initialize zlib stream\n"); -+ g_free(gzs); -+ return NULL; -+ } - -- gzs->decompressor = g_zlib_decompressor_new(format); - gzs->max_output = max_output; - - return gzs; -@@ -379,6 +389,7 @@ - const gchar *compressed_buff; - gsize compressed_len; - GString *ret; -+ z_stream *zs; - - g_return_val_if_fail(gzs != NULL, NULL); - g_return_val_if_fail(buf != NULL, NULL); -@@ -386,6 +397,8 @@ - if (gzs->failed) - return NULL; - -+ zs = &gzs->zs; -+ - if (gzs->pending) { - g_string_append_len(gzs->pending, buf, len); - compressed_buff = gzs->pending->str; -@@ -395,26 +408,22 @@ - compressed_len = len; - } - -+ zs->next_in = (z_const Bytef*)compressed_buff; -+ zs->avail_in = compressed_len; -+ - ret = g_string_new(NULL); -- while (compressed_len > 0) { -- GConverterResult gzres; -+ while (zs->avail_in > 0) { -+ int gzres; - gchar decompressed_buff[PURPLE_HTTP_GZ_BUFF_LEN]; -- gsize decompressed_len = 0; -- gsize bytes_read = 0; -- GError *error = NULL; -- -- gzres = g_converter_convert(G_CONVERTER(gzs->decompressor), -- compressed_buff, compressed_len, -- decompressed_buff, sizeof(decompressed_buff), -- G_CONVERTER_NO_FLAGS, -- &bytes_read, -- &decompressed_len, -- &error); -+ gsize decompressed_len; - -- compressed_buff += bytes_read; -- compressed_len -= bytes_read; -+ zs->next_out = (Bytef*)decompressed_buff; -+ zs->avail_out = sizeof(decompressed_buff); -+ decompressed_len = zs->avail_out = sizeof(decompressed_buff); -+ gzres = inflate(zs, Z_FULL_FLUSH); -+ decompressed_len -= zs->avail_out; - -- if (gzres == G_CONVERTER_CONVERTED || G_CONVERTER_FINISHED) { -+ if (gzres == Z_OK || gzres == Z_STREAM_END) { - if (decompressed_len == 0) - break; - if (gzs->decompressed + decompressed_len >= -@@ -424,18 +433,17 @@ - " decompressed data is reached\n"); - decompressed_len = gzs->max_output - - gzs->decompressed; -- gzres = G_CONVERTER_FINISHED; -+ gzres = Z_STREAM_END; - } - gzs->decompressed += decompressed_len; - g_string_append_len(ret, decompressed_buff, - decompressed_len); -- if (gzres == G_CONVERTER_FINISHED) -+ if (gzres == Z_STREAM_END) - break; - } else { - purple_debug_error("http", - "Decompression failed (%d): %s\n", gzres, -- error->message); -- g_clear_error(&error); -+ zs->msg); - gzs->failed = TRUE; - return NULL; - } -@@ -446,9 +454,9 @@ - gzs->pending = NULL; - } - -- if (compressed_len > 0) { -- gzs->pending = g_string_new_len(compressed_buff, -- compressed_len); -+ if (zs->avail_in > 0) { -+ gzs->pending = g_string_new_len((gchar*)zs->next_in, -+ zs->avail_in); - } - - return ret; -@@ -459,7 +467,7 @@ - { - if (gzs == NULL) - return; -- g_object_unref(gzs->decompressor); -+ inflateEnd(&gzs->zs); - if (gzs->pending) - g_string_free(gzs->pending, TRUE); - g_free(gzs); diff --git a/patches/10-glib-compiled-with-debug.patch b/patches/10-glib-compiled-with-debug.patch deleted file mode 100644 index 8c92d6c9..00000000 --- a/patches/10-glib-compiled-with-debug.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 99e31624bf9e88b3002e05514db904d5aad35db6 Mon Sep 17 00:00:00 2001 -From: dequis -Date: Sat, 25 Feb 2017 03:14:57 -0300 -Subject: [PATCH] Fix crash when the error signal is raised and glib has - G_ENABLE_DEBUG - -If glib is built with --enable-debug=yes, it will have G_ENABLE_DEBUG -set internally - -This flag changes the g_marshal_value_peek_object() macro to use -g_value_get_object(), which performs sanity checks, instead of doing a -direct offset access. - -The definition of our error signal was wrong, using a marshaller with -a GObject in the first parameter but setting the type of the GValue to -G_TYPE_ERROR. Since we have no marshaller for G_TYPE_ERROR, and that all -of those are functionally equivalent (and in fact use the exact same -code in non-debug builds), I went with POINTER for both sides. - -The actual crash happened because of a g_return_val_if_fail() in the -sanity checks which made it return NULL after throwing a warning like -this: - - g_value_get_object: assertion 'G_VALUE_HOLDS_OBJECT (value)' failed - -This was reported by a gentoo user who had the debug use flag. Thanks! ---- - facebook/facebook-api.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/libpurple/protocols/facebook/api.c b/libpurple/protocols/facebook/api.c -index a94f9af..eb0e8b4 100644 ---- a/libpurple/protocols/facebook/api.c -+++ b/libpurple/protocols/facebook/api.c -@@ -381,7 +381,7 @@ - G_SIGNAL_ACTION, - 0, - NULL, NULL, -- fb_marshal_VOID__OBJECT, -+ fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - diff --git a/patches/11-contacts.patch b/patches/11-contacts.patch deleted file mode 100644 index bcebf89f..00000000 --- a/patches/11-contacts.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/libpurple/protocols/facebook/api.c -+++ b/libpurple/protocols/facebook/api.c -@@ -2424,7 +2424,7 @@ - priv->contacts_delta = g_strdup(is_delta ? cursor : delta_cursor); - } - -- if (users) { -+ if (users || (complete && !is_delta)) { - g_signal_emit_by_name(api, "contacts", users, complete); - } - diff --git a/patches/12-glib-error-cast-bug-2-electric-boogaloo.patch b/patches/12-glib-error-cast-bug-2-electric-boogaloo.patch deleted file mode 100644 index 08d4476d..00000000 --- a/patches/12-glib-error-cast-bug-2-electric-boogaloo.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/libpurple/protocols/facebook/mqtt.c -+++ b/libpurple/protocols/facebook/mqtt.c -@@ -113,9 +113,9 @@ - G_SIGNAL_ACTION, - 0, - NULL, NULL, -- fb_marshal_VOID__OBJECT, -+ fb_marshal_VOID__POINTER, - G_TYPE_NONE, -- 1, G_TYPE_ERROR); -+ 1, G_TYPE_POINTER); - - /** - * FbMqtt::open: diff --git a/patches/13-async-sockets-are-hard.patch b/patches/13-async-sockets-are-hard.patch deleted file mode 100644 index 65752943..00000000 --- a/patches/13-async-sockets-are-hard.patch +++ /dev/null @@ -1,51 +0,0 @@ ---- a/libpurple/protocols/facebook/mqtt.c -+++ b/libpurple/protocols/facebook/mqtt.c -@@ -361,26 +361,33 @@ - g_byte_array_set_size(priv->rbuf, 0); - - res = purple_ssl_read(priv->gsc, &byte, sizeof byte); -- g_byte_array_append(priv->rbuf, &byte, sizeof byte); - -- if (res != sizeof byte) { -+ if (res < 0 && errno == EAGAIN) { -+ return; -+ } else if (res != 1) { - fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, - _("Failed to read fixed header")); - return; - } - -+ g_byte_array_append(priv->rbuf, &byte, sizeof byte); -+ - mult = 1; - - do { - res = purple_ssl_read(priv->gsc, &byte, sizeof byte); -- g_byte_array_append(priv->rbuf, &byte, sizeof byte); - -- if (res != sizeof byte) { -+ /* TODO: this case isn't handled yet */ -+ if (0 && res < 0 && errno == EAGAIN) { -+ return; -+ } else if (res != 1) { - fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, - _("Failed to read packet size")); - return; - } - -+ g_byte_array_append(priv->rbuf, &byte, sizeof byte); -+ - priv->remz += (byte & 127) * mult; - mult *= 128; - } while ((byte & 128) != 0); -@@ -390,7 +397,9 @@ - size = MIN(priv->remz, sizeof buf); - rize = purple_ssl_read(priv->gsc, buf, size); - -- if (rize < 1) { -+ if (rize < 0 && errno == EAGAIN) { -+ return; -+ } else if (rize < 1) { - fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, - _("Failed to read packet data")); - return; diff --git a/patches/14-the-future.patch b/patches/14-the-future.patch deleted file mode 100644 index 6a8c7949..00000000 --- a/patches/14-the-future.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- old/libpurple/protocols/facebook/api.h 2019-01-07 10:30:23.164785430 +0100 -+++ pidgin/libpurple/protocols/facebook/api.h 2019-01-07 10:35:18.494967061 +0100 -@@ -104,7 +104,7 @@ - * server started checking this. - */ - --#define FB_ORCA_AGENT "[FBAN/Orca-Android;FBAV/109.0.0.17.70;FBPN/com.facebook.orca;FBLC/en_US;FBBV/52182662]" -+#define FB_ORCA_AGENT "[FBAN/Orca-Android;FBAV/192.0.0.31.101;FBPN/com.facebook.orca;FBLC/en_US;FBBV/52182662]" - - /** - * FB_API_AGENT: diff --git a/patches/15-glib-deprecate-g_type_class_add_private.patch b/patches/15-glib-deprecate-g_type_class_add_private.patch deleted file mode 100644 index c8709c16..00000000 --- a/patches/15-glib-deprecate-g_type_class_add_private.patch +++ /dev/null @@ -1,99 +0,0 @@ -Index: libpurple/protocols/facebook/api.c -=================================================================== ---- a/libpurple/protocols/facebook/api.c -+++ b/libpurple/protocols/facebook/api.c -@@ -92,7 +92,7 @@ fb_api_sticker(FbApi *api, FbId sid, FbA - void - fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor); - --G_DEFINE_TYPE(FbApi, fb_api, G_TYPE_OBJECT); -+G_DEFINE_TYPE_WITH_CODE(FbApi, fb_api, G_TYPE_OBJECT, G_ADD_PRIVATE(FbApi)); - - static void - fb_api_set_property(GObject *obj, guint prop, const GValue *val, -Index: libpurple/protocols/facebook/data.c -=================================================================== ---- a/libpurple/protocols/facebook/data.c -+++ b/libpurple/protocols/facebook/data.c -@@ -59,8 +59,8 @@ static const gchar *fb_props_strs[] = { - "token" - }; - --G_DEFINE_TYPE(FbData, fb_data, G_TYPE_OBJECT); --G_DEFINE_TYPE(FbDataImage, fb_data_image, G_TYPE_OBJECT); -+G_DEFINE_TYPE_WITH_CODE(FbData, fb_data, G_TYPE_OBJECT, G_ADD_PRIVATE(FbData)); -+G_DEFINE_TYPE_WITH_CODE(FbDataImage, fb_data_image, G_TYPE_OBJECT, G_ADD_PRIVATE(FbDataImage)); - - static void - fb_data_dispose(GObject *obj) -Index: libpurple/protocols/facebook/json.c -=================================================================== ---- a/libpurple/protocols/facebook/json.c -+++ b/libpurple/protocols/facebook/json.c -@@ -25,6 +25,7 @@ - #include - - #include "json.h" -+#include "glibcompat.h" - #include "util.h" - - typedef struct _FbJsonValue FbJsonValue; -@@ -50,7 +51,7 @@ struct _FbJsonValuesPrivate - GError *error; - }; - --G_DEFINE_TYPE(FbJsonValues, fb_json_values, G_TYPE_OBJECT); -+G_DEFINE_TYPE_WITH_CODE(FbJsonValues, fb_json_values, G_TYPE_OBJECT, G_ADD_PRIVATE(FbJsonValues)); - - static void - fb_json_values_dispose(GObject *obj) -Index: libpurple/protocols/facebook/mqtt.c -=================================================================== ---- a/libpurple/protocols/facebook/mqtt.c -+++ b/libpurple/protocols/facebook/mqtt.c -@@ -62,8 +62,8 @@ struct _FbMqttMessagePrivate - gboolean local; - }; - --G_DEFINE_TYPE(FbMqtt, fb_mqtt, G_TYPE_OBJECT); --G_DEFINE_TYPE(FbMqttMessage, fb_mqtt_message, G_TYPE_OBJECT); -+G_DEFINE_TYPE_WITH_CODE(FbMqtt, fb_mqtt, G_TYPE_OBJECT, G_ADD_PRIVATE(FbMqtt)); -+G_DEFINE_TYPE_WITH_CODE(FbMqttMessage, fb_mqtt_message, G_TYPE_OBJECT, G_ADD_PRIVATE(FbMqttMessage)); - - static void - fb_mqtt_dispose(GObject *obj) -Index: libpurple/protocols/facebook/thrift.c -=================================================================== ---- a/libpurple/protocols/facebook/thrift.c -+++ b/libpurple/protocols/facebook/thrift.c -@@ -21,6 +21,7 @@ - - #include - -+#include "glibcompat.h" - #include "thrift.h" - - struct _FbThriftPrivate -@@ -32,7 +33,7 @@ struct _FbThriftPrivate - guint lastbool; - }; - --G_DEFINE_TYPE(FbThrift, fb_thrift, G_TYPE_OBJECT); -+G_DEFINE_TYPE_WITH_CODE(FbThrift, fb_thrift, G_TYPE_OBJECT, G_ADD_PRIVATE(FbThrift)); - - static void - fb_thrift_dispose(GObject *obj) -Index: libpurple/glibcompat.h -=================================================================== ---- a/libpurple/glibcompat.h -+++ b/libpurple/glibcompat.h -@@ -110,6 +110,9 @@ static inline void g_queue_free_full(GQu - g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ - "'" #expr "' should be NULL"); \ - } G_STMT_END -+#define G_ADD_PRIVATE(TypeName) G_STMT_START { } G_STMT_END -+#else -+#define g_type_class_add_private(k,s) G_STMT_START { } G_STMT_END - #endif - - #if !GLIB_CHECK_VERSION(2, 40, 0) diff --git a/patches/16-fix-duplicate-decl-specifier.patch b/patches/16-fix-duplicate-decl-specifier.patch deleted file mode 100644 index d3a68e98..00000000 --- a/patches/16-fix-duplicate-decl-specifier.patch +++ /dev/null @@ -1,13 +0,0 @@ -Index: libpurple/protocols/facebook/http.c -=================================================================== ---- a/libpurple/protocols/facebook/http.c -+++ b/libpurple/protocols/facebook/http.c -@@ -381,7 +381,7 @@ fb_http_urlcmp(const gchar *url1, const - PurpleHttpURL *purl1; - PurpleHttpURL *purl2; - -- static const const gchar * (*funcs[]) (const PurpleHttpURL *url) = { -+ static const gchar * (*funcs[]) (const PurpleHttpURL *url) = { - /* Always first so it can be skipped */ - purple_http_url_get_protocol, - diff --git a/patches/17-this-build-system-sucks.patch b/patches/17-this-build-system-sucks.patch deleted file mode 100644 index eb75a267..00000000 --- a/patches/17-this-build-system-sucks.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff -Naur pidgin/libpurple/protocols/facebook/api.h pidgin2/libpurple/protocols/facebook/api.h ---- a/libpurple/protocols/facebook/api.h -+++ b/libpurple/protocols/facebook/api.h -@@ -111,7 +111,7 @@ - * - * The HTTP User-Agent header. - */ --#define FB_API_AGENT "Facebook plugin / Purple / 0.9.5 " FB_ORCA_AGENT -+#define FB_API_AGENT "Facebook plugin / Purple / " PACKAGE_VERSION " " FB_ORCA_AGENT - - /** - * FB_API_MQTT_AGENT diff --git a/patches/18-fix-thrift-stop-failure.patch b/patches/18-fix-thrift-stop-failure.patch deleted file mode 100644 index a8af8e42..00000000 --- a/patches/18-fix-thrift-stop-failure.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/libpurple/protocols/facebook/api.c -+++ b/libpurple/protocols/facebook/api.c -@@ -1847,7 +1847,7 @@ - fb_util_debug_info("Presence: %" FB_ID_FORMAT " (%d)", - i64, i32 != 0); - -- while (id <= 5) { -+ while (id <= 6) { - if (fb_thrift_read_isstop(thft)) { - break; - } diff --git a/patches/19-fix-taNewMessage-bug.patch b/patches/19-fix-taNewMessage-bug.patch deleted file mode 100644 index c2aaa9f6..00000000 --- a/patches/19-fix-taNewMessage-bug.patch +++ /dev/null @@ -1,64 +0,0 @@ ---- a/libpurple/protocols/facebook/api.c -+++ b/libpurple/protocols/facebook/api.c -@@ -1502,6 +1502,23 @@ - fb_api_cb_publish_ms_event(FbApi *api, JsonNode *root, GSList *events, FbApiEventType type, GError **error); - - static void -+fb_api_cb_publish_mst(FbThrift *thft, GError **error) -+{ -+ if (fb_thrift_read_isstop(thft)) { -+ FB_API_TCHK(fb_thrift_read_stop(thft)); -+ } else { -+ FbThriftType type; -+ gint16 id; -+ -+ FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); -+ FB_API_TCHK(type == FB_THRIFT_TYPE_STRING); -+ // FB_API_TCHK(id == 2); -+ FB_API_TCHK(fb_thrift_read_str(thft, NULL)); -+ FB_API_TCHK(fb_thrift_read_stop(thft)); -+ } -+} -+ -+static void - fb_api_cb_publish_ms(FbApi *api, GByteArray *pload) - { - const gchar *data; -@@ -1531,10 +1548,14 @@ - - /* Read identifier string (for Facebook employees) */ - thft = fb_thrift_new(pload, 0); -- fb_thrift_read_str(thft, NULL); -+ fb_api_cb_publish_mst(thft, &err); - size = fb_thrift_get_pos(thft); - g_object_unref(thft); - -+ FB_API_ERROR_EMIT(api, err, -+ return; -+ ); -+ - g_return_if_fail(size < pload->len); - data = (gchar *) pload->data + size; - size = pload->len - size; -@@ -1844,8 +1865,8 @@ - pres->active = i32 != 0; - *press = g_slist_prepend(*press, pres); - -- fb_util_debug_info("Presence: %" FB_ID_FORMAT " (%d)", -- i64, i32 != 0); -+ fb_util_debug_info("Presence: %" FB_ID_FORMAT " (%d) id: %d", -+ i64, i32 != 0, id); - - while (id <= 6) { - if (fb_thrift_read_isstop(thft)) { -@@ -1894,7 +1915,9 @@ - } - - /* Read the field stop */ -- FB_API_TCHK(fb_thrift_read_stop(thft)); -+ if (fb_thrift_read_isstop(thft)) { -+ FB_API_TCHK(fb_thrift_read_stop(thft)); -+ } - } - - static void diff --git a/patches/20-bump-FB_ORCA_AGENT-version.patch b/patches/20-bump-FB_ORCA_AGENT-version.patch deleted file mode 100644 index f237bc31..00000000 --- a/patches/20-bump-FB_ORCA_AGENT-version.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/libpurple/protocols/facebook/api.h -+++ b/libpurple/protocols/facebook/api.h -@@ -97,7 +97,7 @@ - * server started checking this. - */ - --#define FB_ORCA_AGENT "[FBAN/Orca-Android;FBAV/192.0.0.31.101;FBPN/com.facebook.orca;FBLC/en_US;FBBV/52182662]" -+#define FB_ORCA_AGENT "[FBAN/Orca-Android;FBAV/537.0.0.31.101;FBPN/com.facebook.orca;FBLC/en_US;FBBV/52182662]" - - /** - * FB_API_AGENT: diff --git a/update.sh b/update.sh deleted file mode 100755 index f2f2a381..00000000 --- a/update.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -set -e - -URL="https://keep.imfreedom.org/pidgin/pidgin" -HASHG=$(_TMP_=$(type hg 2>&1); echo $?) - -if test "$HASHG" != "0"; then - echo "hg (mercurial) not found in PATH" >&2 - exit $HASHG -fi - -test -z "$srcdir" && srcdir=$(dirname "$0") -test -z "$srcdir" && srcdir=. -test -z "$pidgindir" && pidgindir=.pidgin - -cd "$srcdir" -REVISION=$(cat VERSION) - -if ! test -d "$pidgindir/.hg"; then - rm -rf "$pidgindir" - hg clone "$URL" "$pidgindir" -fi - -hg -R "$pidgindir" -v pull -hg -R "$pidgindir" -v update -C "$REVISION" -rm -rf pidgin - -for FILE in $(cat MANIFEST_PIDGIN); do - mkdir -p $(dirname "pidgin/$FILE") - cp "$pidgindir/$FILE" "pidgin/$FILE" -done - -touch $(cat MANIFEST_VOIDS) - -patchdir="$(pwd)/patches" -cd pidgin - -for patch in $(ls -1 "$patchdir"); do - patch -p1 -i "$patchdir/$patch" -done From 67658ccf155fbf329c1fb536d9a1a8d297cc46b4 Mon Sep 17 00:00:00 2001 From: James Carthew Date: Mon, 1 Apr 2024 22:16:56 +1030 Subject: [PATCH 02/26] - Updated repository. --- meson.build | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/meson.build b/meson.build index 4eb5ca84..36c293b4 100644 --- a/meson.build +++ b/meson.build @@ -25,21 +25,6 @@ if get_option('warnings') add_project_arguments('-Wall', '-Wextra', '-Waggregate-return', '-Wdeclaration-after-statement', '-Wfloat-equal', '-Wformat', '-Winit-self', '-Wmissing-declarations', '-Wmissing-prototypes', '-Wno-unused-parameter', '-Wpointer-arith', language : 'c') endif -# Define source files -source_files = files( - 'pidgin/libpurple/protocols/facebook/marshal.c', - 'pidgin/libpurple/protocols/facebook/api.c', - 'pidgin/libpurple/protocols/facebook/data.c', - 'pidgin/libpurple/protocols/facebook/facebook.c', - 'pidgin/libpurple/protocols/facebook/http.c', - 'pidgin/libpurple/protocols/facebook/json.c', - 'pidgin/libpurple/protocols/facebook/mqtt.c', - 'pidgin/libpurple/protocols/facebook/thrift.c', - 'pidgin/libpurple/protocols/facebook/util.c', - 'pidgin/libpurple/http.c', - 'pidgin/libpurple/purple-socket.c' -) - # Argument for specifying the libpurple plugin directory plugin_dir = get_option('plugindir') @@ -71,6 +56,21 @@ run_command(generate_marshal_header_command, run_command(generate_marshal_c_command, check: true) +# Define source files +source_files = files( + 'pidgin/libpurple/protocols/facebook/marshal.c', + 'pidgin/libpurple/protocols/facebook/api.c', + 'pidgin/libpurple/protocols/facebook/data.c', + 'pidgin/libpurple/protocols/facebook/facebook.c', + 'pidgin/libpurple/protocols/facebook/http.c', + 'pidgin/libpurple/protocols/facebook/json.c', + 'pidgin/libpurple/protocols/facebook/mqtt.c', + 'pidgin/libpurple/protocols/facebook/thrift.c', + 'pidgin/libpurple/protocols/facebook/util.c', + 'pidgin/libpurple/http.c', + 'pidgin/libpurple/purple-socket.c' +) + # Determine the plugin directory if not provided by the user if plugin_dir == '' purple_plugindir = '' From c6b0a45c13af90ee57a7707f57e679e64acec283 Mon Sep 17 00:00:00 2001 From: James Carthew Date: Mon, 1 Apr 2024 22:21:06 +1030 Subject: [PATCH 03/26] Added missing files from repository. --- .gitignore | 23 - include/blistnode.h | 0 include/buddy.h | 0 include/buddylist.h | 0 include/conversations.h | 0 include/conversationtypes.h | 0 include/image-store.h | 0 include/image.h | 0 include/message.h | 0 include/plugins.h | 0 include/presence.h | 0 include/protocol.h | 0 include/protocols.h | 0 pidgin/AUTHORS | 146 + pidgin/COPYING | 339 ++ pidgin/COPYRIGHT | 643 +++ .../libpurple/.deps/libfacebook_la-http.Plo | 1 + .../.deps/libfacebook_la-purple-socket.Plo | 1 + pidgin/libpurple/glibcompat.h | 139 + pidgin/libpurple/http.c | 3299 +++++++++++++++ pidgin/libpurple/http.h | 964 +++++ pidgin/libpurple/protocols/facebook/Makefile | 890 +++++ .../libpurple/protocols/facebook/Makefile.am | 70 + .../libpurple/protocols/facebook/Makefile.in | 890 +++++ .../protocols/facebook/Makefile.mingw | 104 + pidgin/libpurple/protocols/facebook/api.c | 3539 +++++++++++++++++ pidgin/libpurple/protocols/facebook/api.h | 1006 +++++ pidgin/libpurple/protocols/facebook/data.c | 608 +++ pidgin/libpurple/protocols/facebook/data.h | 398 ++ .../libpurple/protocols/facebook/facebook.c | 1685 ++++++++ .../libpurple/protocols/facebook/facebook.h | 34 + pidgin/libpurple/protocols/facebook/http.c | 438 ++ pidgin/libpurple/protocols/facebook/http.h | 371 ++ pidgin/libpurple/protocols/facebook/id.h | 131 + pidgin/libpurple/protocols/facebook/json.c | 677 ++++ pidgin/libpurple/protocols/facebook/json.h | 517 +++ pidgin/libpurple/protocols/facebook/marshal.c | 195 + pidgin/libpurple/protocols/facebook/marshal.h | 57 + .../protocols/facebook/marshaller.list | 7 + pidgin/libpurple/protocols/facebook/mqtt.c | 1023 +++++ pidgin/libpurple/protocols/facebook/mqtt.h | 626 +++ pidgin/libpurple/protocols/facebook/thrift.c | 700 ++++ pidgin/libpurple/protocols/facebook/thrift.h | 604 +++ pidgin/libpurple/protocols/facebook/util.c | 566 +++ pidgin/libpurple/protocols/facebook/util.h | 350 ++ pidgin/libpurple/purple-socket.c | 410 ++ pidgin/libpurple/purple-socket.h | 217 + 47 files changed, 21645 insertions(+), 23 deletions(-) create mode 100644 include/blistnode.h create mode 100644 include/buddy.h create mode 100644 include/buddylist.h create mode 100644 include/conversations.h create mode 100644 include/conversationtypes.h create mode 100644 include/image-store.h create mode 100644 include/image.h create mode 100644 include/message.h create mode 100644 include/plugins.h create mode 100644 include/presence.h create mode 100644 include/protocol.h create mode 100644 include/protocols.h create mode 100644 pidgin/AUTHORS create mode 100644 pidgin/COPYING create mode 100644 pidgin/COPYRIGHT create mode 100644 pidgin/libpurple/.deps/libfacebook_la-http.Plo create mode 100644 pidgin/libpurple/.deps/libfacebook_la-purple-socket.Plo create mode 100644 pidgin/libpurple/glibcompat.h create mode 100644 pidgin/libpurple/http.c create mode 100644 pidgin/libpurple/http.h create mode 100644 pidgin/libpurple/protocols/facebook/Makefile create mode 100644 pidgin/libpurple/protocols/facebook/Makefile.am create mode 100644 pidgin/libpurple/protocols/facebook/Makefile.in create mode 100644 pidgin/libpurple/protocols/facebook/Makefile.mingw create mode 100644 pidgin/libpurple/protocols/facebook/api.c create mode 100644 pidgin/libpurple/protocols/facebook/api.h create mode 100644 pidgin/libpurple/protocols/facebook/data.c create mode 100644 pidgin/libpurple/protocols/facebook/data.h create mode 100644 pidgin/libpurple/protocols/facebook/facebook.c create mode 100644 pidgin/libpurple/protocols/facebook/facebook.h create mode 100644 pidgin/libpurple/protocols/facebook/http.c create mode 100644 pidgin/libpurple/protocols/facebook/http.h create mode 100644 pidgin/libpurple/protocols/facebook/id.h create mode 100644 pidgin/libpurple/protocols/facebook/json.c create mode 100644 pidgin/libpurple/protocols/facebook/json.h create mode 100644 pidgin/libpurple/protocols/facebook/marshal.c create mode 100644 pidgin/libpurple/protocols/facebook/marshal.h create mode 100644 pidgin/libpurple/protocols/facebook/marshaller.list create mode 100644 pidgin/libpurple/protocols/facebook/mqtt.c create mode 100644 pidgin/libpurple/protocols/facebook/mqtt.h create mode 100644 pidgin/libpurple/protocols/facebook/thrift.c create mode 100644 pidgin/libpurple/protocols/facebook/thrift.h create mode 100644 pidgin/libpurple/protocols/facebook/util.c create mode 100644 pidgin/libpurple/protocols/facebook/util.h create mode 100644 pidgin/libpurple/purple-socket.c create mode 100644 pidgin/libpurple/purple-socket.h diff --git a/.gitignore b/.gitignore index a632a291..e69de29b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +0,0 @@ -*.la -*.lo -*.o -*.tar.* -.deps -.libs -.pidgin -aclocal.m4 -autom4te.cache -build-aux -config.log -config.status -configure -debian -include -INSTALL -libtool -libtool.m4 -lt*.m4 -Makefile -Makefile.in -pidgin -win32-install-dir diff --git a/include/blistnode.h b/include/blistnode.h new file mode 100644 index 00000000..e69de29b diff --git a/include/buddy.h b/include/buddy.h new file mode 100644 index 00000000..e69de29b diff --git a/include/buddylist.h b/include/buddylist.h new file mode 100644 index 00000000..e69de29b diff --git a/include/conversations.h b/include/conversations.h new file mode 100644 index 00000000..e69de29b diff --git a/include/conversationtypes.h b/include/conversationtypes.h new file mode 100644 index 00000000..e69de29b diff --git a/include/image-store.h b/include/image-store.h new file mode 100644 index 00000000..e69de29b diff --git a/include/image.h b/include/image.h new file mode 100644 index 00000000..e69de29b diff --git a/include/message.h b/include/message.h new file mode 100644 index 00000000..e69de29b diff --git a/include/plugins.h b/include/plugins.h new file mode 100644 index 00000000..e69de29b diff --git a/include/presence.h b/include/presence.h new file mode 100644 index 00000000..e69de29b diff --git a/include/protocol.h b/include/protocol.h new file mode 100644 index 00000000..e69de29b diff --git a/include/protocols.h b/include/protocols.h new file mode 100644 index 00000000..e69de29b diff --git a/pidgin/AUTHORS b/pidgin/AUTHORS new file mode 100644 index 00000000..a0da4927 --- /dev/null +++ b/pidgin/AUTHORS @@ -0,0 +1,146 @@ +Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +========================================================================== + +For a complete list of all contributors, see the COPYRIGHT file. + +We've got an IRC room now too, #pidgin on irc.freenode.net. Come check us out. + +Current Developers: +------------------ +Daniel 'datallah' Atallah - Developer +Paul 'darkrain42' Aurich - Developer +Ethan 'Paco-Paco' Blanton - Developer +Sadrul Habib Chowdhury - Developer +Gary 'grim' Kramlich - Maintainer +Richard 'rlaager' Laager - Developer +Marcus 'malu' Lundblad - Developer +Sulabh 'sulabh_m' Mahajan - Developer +Richard 'wabz' Nelson - Developer +Etan 'deryni' Reisner - Developer +Michael 'Maiku' Ruprecht - Developer, voice and video +Elliott 'QuLogic' Sales de Andrade - Developer +Evan Schoenberg - Developer +Kevin 'SimGuy' Stange - Developer & Webmaster +Will 'resiak' Thompson - Developer +Stu 'nosnilmot' Tomlinson - Developer +Jorge 'Masca' Villaseñor - Developer +Tomasz Wasilczyk - Developer + +Crazy Patch Writers: +------------------- +Jakub 'haakon' Adam +James 'jgeboski' Geboski +Krzysztof Klinikowski +Eion Robb +Ankit Vani + +Retired Developers: +------------------ +John 'rekkanoryo' Bailey - Developer +Herman Bloggs - Win32 Port +Thomas Butter - Developer +Ka-Hing Cheung - Developer +Mark 'KingAnt' Doliner - maintainer +Jim Duchek - maintainer +Sean Egan - Developer +Rob Flynn - maintainer +Adam Fritzler - libfaim maintainer +Christian 'ChipX86' Hammond - Developer & Webmaster +Casey Harkins - Developer +Ivan Komarov - Developer +Syd Logan - hacker and designated driver [lazy bum] +Christopher 'siege' O'Brien - Developer +Bartosz Oler - Developer +Tim 'marv' Ringenbach - Developer +Luke 'LSchiere' Schierer - Support +Megan 'Cae' Schneider - support/QA +Jim Seymour - XMPP developer +Mark Spencer - original author +Nathan 'faceprint' Walp - Developer +Eric Warmenhoven - lead developer + +Retired Crazy Patch Writers: +--------------------------- +Felipe 'shx' Contreras +Decklin Foster +Dennis 'EvilDennisR' Ristuccia - Senior Contributor/QA +Peter 'Bleeter' Lawler +Robert 'Robot101' McQueen +Benjamin Miller +Peter 'Fmoo' Ruibal +Gabriel 'Nix' Schulhof + +Artists: +------- +Hylke Bons - Icons + +Other Contributions: +------------------- +Much thanks to Evan Martin for writing +GtkSpell responsible for the +"Highlight misspelled words" feature and for gtk-nativewin + the default GTK+-2.0 +engine originally used in our win32 port. + +** ORIGINAL LOGO DESIGNED BY: Naru Sundar ** + +Peter Teichiman +Larry Ewing +Jeramey A. Crawford + Thanks to these boys. Peter and Larry managed to stomp + out a large list of Mem Leaks. Jeramey found the remaining + onees and pointed me to those. Props to the boys at + Helix Code. Thanks guys. + +Nathan Walp + A healthy amount of patches for the Jabber plugin + +Neil Sanchala + Wrote most of the Zephyr plugin + +Arkadiusz Miskiewicz + Wrote the Gadu-Gadu plugin + +David Prater draven@tcsx.net + Log and Colour Button Images + +Sébastien Carpe + Base HTTP Proxy Support + +Ari Pollak compwiz.dhs.org + Resize conversation window patch + +Decklin Foster + Many GUI improvements, other nifty additions and fixes + +David + The neato-bigger text box + +S D Erle + Writing a cool perl script to translate WinAIM lists to gaim + +BMiller + A good collection of stuff. %n for away messages, import winaim + lists, pic/text/pic+text for buttons, among others + +Lance Rocker + Improved HTML formatting in logs, plus lots of debugging on *BSD. + +ergofobe: + GNOME Url handler patch + +Justin M. Ward : + Alphabetical Away Messages patch + +G. Sumner Hayes Security Patches + +Brian Ryner for a little make file patch :) + +Ryan C. Gordon - I still think you look like Silent Bob. + +Elliot Tobin + +Thanks to Jeroen van der Vegt for the initial smiley plugin and images. + +The OpenQ Team + Wrote the QQ plugin dropped in 2.8.0 (see libpurple/qq/AUTHORS in 2.7.11) diff --git a/pidgin/COPYING b/pidgin/COPYING new file mode 100644 index 00000000..d511905c --- /dev/null +++ b/pidgin/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/pidgin/COPYRIGHT b/pidgin/COPYRIGHT new file mode 100644 index 00000000..998324c6 --- /dev/null +++ b/pidgin/COPYRIGHT @@ -0,0 +1,643 @@ +Pidgin, Finch, and libpurple + +This file is intended to be a comprehensive list of contributors to +this project. If you have contributed to this project then you deserve +to be on this list. Contact us (see: AUTHORS) and we'll add you. + +Many open source projects list contributor names at the top of each +source file containing their contribution. However, we've found +that it is difficult to keep this list accurate, especially when old +code is removed or existing code is moved to a different file. So +instead we chose to list a generic message at the top of each source +file that points here. + +If concerns are raised as to the copyright holder of a particular +piece of code, then that code should be traced through our version +control system to see from where it came and who has modified it. + +Copyright (C) 1998-2014 by the following: + +Mark +Saleem Abdulrasool +Jakub Adam +Dave Ahlswede +Haval A. Ahmed +Sorokin Alexei +Thijs Alkemade +Manuel Amador +Matt Amato +Josef Andrysek +Flavius Anton +Geoffrey Antos +Daniel Atallah +Paul Aurich +Patrick Aussems +Anibal Avelar +Ali Albazaz +Kosta Arvanitis +Christopher Ayoup +Alex Badea +John Bailey +Arunan Balasubramaniam +R. Tyler Ballance +Chris Banal +Luca Barbato +Levi Bard +Mark Barfield +Ryan Barrett +Kevin Barry +Lukas Barth +Derek Battams +Martin Bayard +Curtis Beattie +Stefan Becker +Carlos Bederian +Dave Bell +Matthew W.S. Bell +Igor Belyi +David Benjamin +Brian Bernas +Vivien Bernet-Rollande +Paul Betts +Runa Bhattacharjee +Jonas Birmé +George-Cristian Bîrzan +Eric Blade +Ethan Blanton +Joshua Blanton +Rainer Blessing +Herman Bloggs +David Blue +Jason Boerner +Hylke Bons +Graham Booker +Paolo Borelli +Julien Bossart +Craig Boston +Éric Boumaour +Chris Boyle +Stanislav Brabec +Bartosz Brachaczek +Quentin Brandon +Derrick J Brashear +Mauro Sérgio Ferreira Brasil +Luke Bratch +Matt Brenneke +Jeremy Brooks +Jonathan Brossard +Jeffery Brown +Philip Brown +Dan Bruce +Guillaume Brunerie +Norbert Buchmuller +Johannes Buchner +Sean Burke +Gabriel Burt +Thomas Butter +Trevor Caira +Andrea Canciani +Damien Carbery +Michael Carlson +Rodrigo Tobar Carrizo +Keegan Carruthers-Smith +Ludovico Cavedon +Steve Cavilia +Julien Cegarra +Matěj Cepl +Cerulean Studios, LLC +Jonathan Champ +Markos Chandras +Matthew Chapman +Christophe Chapuis +Tirtha Chatterjee +Patrick Cheung +Ka-Hing Cheung +Sadrul Habib Chowdhury +Brian Chu +Howard Chu +Arturo Cisneros, Jr. +Vincas Ciziunas +Jonathan Clark +Joe Clarke +Eoin Coffey +Jason Cohen +Todd Cohen +Graham Cole +Jono Cole +Lorenzo Colitti +Collabora Ltd. +Jeff Connelly +Chris Connett +Nathan Conrad +Felipe Contreras +Alex Converse +Irving Cordova +Glauber de Oliveira Costa +Adam Cowell +Palmer Cox +Jeramey Crawford +Olivier Crete +Michael Culbertson +Steven Danna +Simon Danner +Chris Davies +Josh Davis +Martijn Dekker +Florian Delizy +Jiri Denemark +Vinicius Depizzol +Marc Dequènes +Philip Derrin +Taso N. Devetzis +Balwinder Singh Dheeman +Chandrakant Dhutadmal +Andrew Dieffenbach +Ingmārs Dīriņš +Finlay Dobbie +Mark Doliner +Nuno Donato +Jim Duchek +Alex Duggan +Tom Dyas +Marc E. +Andrew Echols +John Eckerdal +Sean Egan +William Ehlhardt +Markus Elfring +Nelson Elhage +Ignacio J. Elia +Kai Engert +Brian Enigma +Mattias Eriksson +Pat Erley +Stefan Esser +Steffen Eschenbacher +Marc Etcheverry +David Everly +Larry Ewing +Facebook, Inc. +Fartash Faghri +Gábor Farkas +Jesse Farmer +Gavan Fantom (gavan) +Leonardo Fernandes +David Fiander +Michael Fiedler +Ryan Flegel +Rob Flynn +Rob Foehl (rwf) +Chris Foote +Alan Ford +Nathan Fredrickson +Chris J. Friesen +Free Software Foundation +Decklin Foster +Francesco Fracassi +Adam Fritzler +Takao Fujiwara +Max G. +Martin von Gagern +François Gagné +Andrew Gaul +Evgueni V. Gavrilov +Ignacy Gawedzki +James Geboski +Georgi Georgiev +Brian Geppert +Emanuele Giaquinta +Thomas Gibson-Robinson +Ike Gingerich +Gustavo Giráldez +Richard Gobeille +Ian Goldberg +Jon Goldberg +Matthew Goldstein +Michael Golden +Issa Gorissen +Charlie Gordon +Ryan C. Gordon +Konrad Gräfe +Miah Gregory +David Grohmann +Christian Grothoff +Vladislav Guberinić +Gideon N. Guillen +Aman Gupta +Ashish Gupta +Christian Hammond +Erick Hamness +Fred Hampton +John Hanauer +Phil Hannent +Casey Harkins +Andy Harrison +Andrew Hart (arhart) +Anders Hasselqvist +Rene Hausleitner +Will Hawkins +G. Sumner Hayes +Michael R. Head +Nick Hebner +Mike Heffner +Justin Heiner +Moos Heintzen +Benjamin Herrenschmidt +Fernando Herrera +hjheins +Hil +Casey Ho +Andrew Hoffman +Iain Holmes +Joshua Honeycutt +Jeffrey Honig +Nigel Horne +Jensen Hornick +Juanjo Molinero Horno +Dustin Howett +Nathanael Hoyle +Greg Hudson +Magnus Hult +Karsten Huneycutt +Andrew Hunt +Kevin Hunter +Rian Hunter +Thomas Huriaux +Instant Messaging Freedom, Inc. +Vitaliy Ischenko +Intel Corporation +Andrew Ivanov +Momchil Ivanov +Scott Jackson +Hans Petter Jansson +David Jedelsky +Henry Jen +Benjamin Kahn +Jan Kaluza +Yuriy Kaminskiy +Anders Kaseorg +Praveen Karadakal +Tomáš Kebert +John Kelm +Jochen Kemnade +Yann Kerherve +Akmal Khushvakov +Gordian Klein +Marten Klencke +Krzysztof Klinikowski +KNTRO +Akuke Kok +Kir Kolyshkin +Ivan Komarov +F.W. Kong +Konstantin Korikov +Cole Kowalski +Nikita Kozlov +Matt Kramer +Gary Kramlich +Jan Kratochvil +Andrej Krivulčík +Patrik Kullman +Sangeeta Kumari +Tuomas Kuosmanen +Tero Kuusela +Richard Laager +Jacky Lam +Scott Lamb +Dennis Lambe Jr. +Joe LaPenna +Steve Láposi +Daniel Larsson +Julia Lawall +Peter Lawler +Vadim Lebedev +Ho-seok Lee +Jean-Yves Lefort +Moses Lei +Ambrose C. Li +Nicolas Lichtmaier +Wesley Lin +Shaun Lindsay +Artem Litvinovich +Josh Littlefield +Daniel Ljungborg +Syd Logan +Lokheed +Norberto Lopes +Shlomi Loubaton +Pieter Loubser +Brian Lu +Uli Luckas +Matthew Luckie +Marcus Lundblad +Mike Lundy +Jason Lynch +Iain MacDonnell +Lucio Maciel +Brian Macke +Paolo Maggi +Sulabh Mahajan +Willian T. Mahan +Jonathan Maltz +Rok Mandeljc +Tobias Markmann +Kris Marsh +Fidel Martinez +Lalo Martins +John Matthews +Simo Mattila +Robert Matusewicz +Michal Matyska +Rudolfs Mazurs +Ryan McCabe +Peter McCurdy +Kurt McKee +James McLaughlin +Torrey McMahon +Greg McNew +Robert McQueen +Mihály Mészáros +Robert Mibus +David Michael +Lars T. Mikkelsen +Mantas Mikulėnas +Benjamin Miller +Kevin Miller +Paul Miller +Arkadiusz Miskiewicz +David Mohr +Kartik Mohta +Andrew Molloy +Tomasz Mon +Michael Monreal +Laurent Montaron +Marco Monteiro +Benjamin Moody +John Moody +Tim Mooney +Sergio Moretto +Nader Morshed +Keith Moyer +Andrei Mozzhuhin +Christian Muise +MXit Lifestyle (Pty) Ltd. +Alexander Nartov +Richard Nelson +Dennis Nezic +Matthew A. Nicholson +Andreas Nilsson +Allan Nordhøy +Henning Norén +Szilard Novaki +Novell +Padraig O'Briain +Christopher O'Brien (siege) +Peter O'Gorman +Jon Oberheide +Marcos García Ochoa +Yusuke Odate +Ruediger Oertel +Gudmundur Bjarni Olafsson +Bartosz Oler +Oliver +The openSUSE Project +Jürgen Orschiedt +Stefan Ott +Shawn Outman +Nathan Owens (pianocomp81) +John Oyler +Matt Pandina +Laszlo Pandy +Giulio 'Twain28' Pascali +Ricardo Fernandez Pascual +Riley Patterson +Havoc Pennington +Ted Percival +Hugo Pereira Da Costa +Eduardo Pérez +Matt Perry +Ani Peter +Luke Petre +Diego Petten +Nathan Peterson +Dmitry Petroff +Sebastián E. Peyrott +Amitakhya Phukan +Andrea Piccinelli +Mateusz Piękos +Celso Pinto +Joao Luís Marques Pinto +Aleksander Piotrowski +Julien Pivotto +Robey Pointer +Eric Polino +Ari Pollak +Stephen Pope +Cristi Posoiu +Alexei Potashnik +Nathan Poznick +Jory A. Pratt +David Preece +Brent Priddy +Justin Pryzby +Florian Quèze +Ignacio Casal Quinteiro +Federicco Mena Quintero +Yosef Radchenko +David Raeman +R. Ramkumar +Rajesh Ranjan +Mart Raudsepp +Etan Reisner +David Reiss +Luoh Ren-Shan +Noa Resare +Tim Retout +Daniele Ricci +Kristian Rietveld +Pekka Riikonen +Tim Ringenbach +Dennis Ristuccia +Lee Roach +Eion Robb +Kahlil Robinson +Rhett Robinson +Luciano Miguel Ferreira Rocha +Andrew Rodland +Miguel Rodríguez (migrax) +Adi Roiban +Martin Rosinski +Bob Rossi +Jason Roth +Jean-Francois Roy +Peter Ruibal +Michael Ruprecht +Sam S. +Thanumalayan S. +Jonathan Sailor +Elliott Sales de Andrade +Catalin Salgau +Tomasz Sałaciński +Pradyumna Sampath +Arvind Samptur +Tom Samstag +Neil Sanchala +Laurent Sansonetti +Andrew Sayman +Alceste Scalas +Carsten Schaar +Toby Schaffer +Jonathan Schleifer +Luke Schierer +Sebastian Schmidt +Ralph Schmieder +David Schmitt +Heiko Schmitt +Mark Schneider +Evan Schoenberg +Gabriel Schulhof +Eric Michael Schwelm +Federico Schwindt +Torrey Searle +Peter Seebach +Don Seiler +Mihai Serban +Leonardo Serra +Matteo Settenvini +Colin Seymour +Jim Seymour +Javeed Shaikh +Joe Shaw +Scott Shedden +Dossy Shiobara +Michael Shkutkov +Shreevatsa R +Dylan Simon +Ettore Simone +Renato Silva +John Silvestri +Mukund Sivaraman +Craig Slusher +Alex Smith +Brad Smith +Malcolm Smith +David Smock +Phil Snowberger +Eddie Sohn (tr1sk) +Sony Computer Entertainment America, Inc. +Andy Spencer +Mark Spencer +Peter Speybrouck +Lex Spoon +Chris Stafford +Kevin Stange +Ferdinand Stehle +Joshua Stein +Jakub Steiner +Richard Stellingwerff +Charlie Stockman +David Stoddard +Adam Strzelecki +Andreas Stührk +Oleg Sukhodolsky +Sun Microsystems +Marcus Sundberg +Mårten Svantesson (fursten) +Amir Szekely (kichik) +Gábor Szuromi (kukkerman) +Jakub Szypulka +Robert T. +Greg Taeger +Rob Taft +Peter Tang +Brian Tarricone +Peter Teichman +Philip Tellis +Michael Terry +Arun A. Tharuvai +Cestonaro Thilo +Will Thompson +Douglas Thrift (douglaswth) +Niels Thykier +Mark Tiefenbruck +Andrew Tinney +Jeffery To +Krzysztof Tobola (kreez) +Warren Togami +Stu Tomlinson +Bill Tompkins +Gal Topper +Chris Toshok +Ken Tossell +Marcus Trautwig +Tom Tromey +Todd Troxell +Brad Turcotte +Kyle Turman +Jon Turney +Junichi Uekawa +Max Ulidtko +Dmitry Utkin +Igor Vlasenko +István Váradi +ILDAR Valeev +Cédric Valmary +Martijn van Beers +Gideon van Melle +Arjan van de Ven +Philip Van Hoof +Ankit Vani +Kristof Vansant +James Vega +David Vermeille +Sid Vicious +Andrew Victor +Jorge Villaseñor (Masca) +Bjoern Voigt +Peter Volkov +Marius Wachtler +Wan Hing Wah +Philip Walford +Nathan Walp +Jonty Wareing +Eric Warmenhoven +Adam J. Warrington +Denis Washington +Tomasz Wasilczyk +Zsombor Welker +Andrew Wellington +Adam Wendt +Simon Wenner +Dave West +Zac West +Daniel Westermann-Clark +Andrew Whewell +Stephen Whitmore +Simon Wilkinson +Dan Willemsen +Dan Williams +Justin Williams (Jaywalker) +Jason Willis +Alex Willmer +Matt Wilson +Dan Winship +Michal Witkowski +Scott Wolchok +Rogier Wolff +The Written Word, Inc. +Kevin Wu Won +Pui Lam Wong +Justin Wood +Ximian +Ma Xuan +Yonas Yanfa +Jared Yanovich +Timmy Yee +Li Yuan +Yuriy Yevgrafov +Jan Zachorowski +Nickolai Zeldovich +Tom Zickel +Marco Ziech +Piotr Zielinski +Jeroen Zwartepoorte diff --git a/pidgin/libpurple/.deps/libfacebook_la-http.Plo b/pidgin/libpurple/.deps/libfacebook_la-http.Plo new file mode 100644 index 00000000..9ce06a81 --- /dev/null +++ b/pidgin/libpurple/.deps/libfacebook_la-http.Plo @@ -0,0 +1 @@ +# dummy diff --git a/pidgin/libpurple/.deps/libfacebook_la-purple-socket.Plo b/pidgin/libpurple/.deps/libfacebook_la-purple-socket.Plo new file mode 100644 index 00000000..9ce06a81 --- /dev/null +++ b/pidgin/libpurple/.deps/libfacebook_la-purple-socket.Plo @@ -0,0 +1 @@ +# dummy diff --git a/pidgin/libpurple/glibcompat.h b/pidgin/libpurple/glibcompat.h new file mode 100644 index 00000000..03826511 --- /dev/null +++ b/pidgin/libpurple/glibcompat.h @@ -0,0 +1,139 @@ +/* pidgin + * + * Pidgin 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 + */ + +#ifndef _GLIBCOMPAT_H_ +#define _GLIBCOMPAT_H_ +/* + * SECTION:glibcompat + * @section_id: libpurple-glibcompat + * @short_description: glibcompat.h + * @title: GLib version-dependent definitions + * + * This file is internal to libpurple. Do not use! + * Also, any public API should not depend on this file. + */ + +#include + +/* glib's definition of g_stat+GStatBuf seems to be broken on mingw64-w32 (and + * possibly other 32-bit windows), so instead of relying on it, + * we'll define our own. + */ +#if defined(_WIN32) && !defined(_MSC_VER) && !defined(_WIN64) +# include +typedef struct _stat GStatBufW32; +static inline int +purple_g_stat(const gchar *filename, GStatBufW32 *buf) +{ + return g_stat(filename, (GStatBuf*)buf); +} +# define GStatBuf GStatBufW32 +# define g_stat purple_g_stat +#endif + + +#ifdef __clang__ + +#undef G_GNUC_BEGIN_IGNORE_DEPRECATIONS +#define G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + _Pragma ("clang diagnostic push") \ + _Pragma ("clang diagnostic ignored \"-Wdeprecated-declarations\"") + +#undef G_GNUC_END_IGNORE_DEPRECATIONS +#define G_GNUC_END_IGNORE_DEPRECATIONS \ + _Pragma ("clang diagnostic pop") + +#endif /* __clang__ */ + + +#if !GLIB_CHECK_VERSION(2, 44, 0) +#define G_IO_ERROR_CONNECTION_CLOSED G_IO_ERROR_BROKEN_PIPE +#endif + +/****************************************************************************** + * g_assert_* macros + *****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 32, 0) +static inline GByteArray * g_byte_array_new_take(guint8 *data, gsize len) +{ + GByteArray *array; + + array = g_byte_array_new(); + g_byte_array_append(array, data, len); + g_free(data); + + return array; +} + +static inline void g_queue_free_full(GQueue *queue, GDestroyNotify free_func) +{ + g_queue_foreach(queue, (GFunc)free_func, NULL); + g_queue_free(queue); +} +#endif + +#if !GLIB_CHECK_VERSION(2, 30, 0) +#define G_VALUE_INIT {0, {{0}}} +#endif + +#if !GLIB_CHECK_VERSION(2, 38, 0) +#define g_assert_true(expr) G_STMT_START { \ + if G_LIKELY (expr) ; else \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "'" #expr "' should be TRUE"); \ + } G_STMT_END +#define g_assert_false(expr) G_STMT_START { \ + if G_LIKELY (!(expr)) ; else \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "'" #expr "' should be FALSE"); \ + } G_STMT_END +#define g_assert_null(expr) G_STMT_START { if G_LIKELY ((expr) == NULL) ; else \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "'" #expr "' should be NULL"); \ + } G_STMT_END +#define G_ADD_PRIVATE(TypeName) G_STMT_START { } G_STMT_END +#else +#define g_type_class_add_private(k,s) G_STMT_START { } G_STMT_END +#endif + +#if !GLIB_CHECK_VERSION(2, 40, 0) +#define g_assert_nonnull(expr) G_STMT_START { \ + if G_LIKELY ((expr) != NULL) ; else \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "'" #expr "' should not be NULL"); \ + } G_STMT_END +#endif + +#if !GLIB_CHECK_VERSION(2, 46, 0) +#define g_assert_cmpmem(m1, l1, m2, l2) G_STMT_START {\ + gconstpointer __m1 = m1, __m2 = m2; \ + int __l1 = l1, __l2 = l2; \ + if (__l1 != __l2) \ + g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #l1 " (len(" #m1 ")) == " #l2 " (len(" #m2 "))", __l1, "==", __l2, 'i'); \ + else if (memcmp (__m1, __m2, __l1) != 0) \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "assertion failed (" #m1 " == " #m2 ")"); \ + } G_STMT_END +#endif + +#endif /* _GLIBCOMPAT_H_ */ diff --git a/pidgin/libpurple/http.c b/pidgin/libpurple/http.c new file mode 100644 index 00000000..122b2a07 --- /dev/null +++ b/pidgin/libpurple/http.c @@ -0,0 +1,3299 @@ +/* purple + * + * 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 "http.h" + +#include "internal.h" +#include "glibcompat.h" + + +#include "debug.h" +#include "proxy.h" +#include "purple-socket.h" + +#include +#ifndef z_const +#define z_const +#endif + +#define PURPLE_HTTP_URL_CREDENTIALS_CHARS "a-z0-9.,~_/*!&%?=+\\^-" +#define PURPLE_HTTP_MAX_RECV_BUFFER_LEN 10240 +#define PURPLE_HTTP_MAX_READ_BUFFER_LEN 10240 +#define PURPLE_HTTP_GZ_BUFF_LEN 1024 + +#define PURPLE_HTTP_REQUEST_DEFAULT_MAX_REDIRECTS 20 +#define PURPLE_HTTP_REQUEST_DEFAULT_TIMEOUT 30 +#define PURPLE_HTTP_REQUEST_DEFAULT_MAX_LENGTH 1048576 +#define PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH G_MAXINT32-1 + +#define PURPLE_HTTP_PROGRESS_WATCHER_DEFAULT_INTERVAL 250000 + +typedef struct _PurpleHttpSocket PurpleHttpSocket; + +typedef struct _PurpleHttpHeaders PurpleHttpHeaders; + +typedef struct _PurpleHttpKeepaliveHost PurpleHttpKeepaliveHost; + +typedef struct _PurpleHttpKeepaliveRequest PurpleHttpKeepaliveRequest; + +typedef struct _PurpleHttpGzStream PurpleHttpGzStream; + +struct _PurpleHttpSocket +{ + PurpleSocket *ps; + + gboolean is_busy; + guint use_count; + PurpleHttpKeepaliveHost *host; +}; + +struct _PurpleHttpRequest +{ + int ref_count; + + gchar *url; + gchar *method; + PurpleHttpHeaders *headers; + PurpleHttpCookieJar *cookie_jar; + PurpleHttpKeepalivePool *keepalive_pool; + + gchar *contents; + int contents_length; + PurpleHttpContentReader contents_reader; + gpointer contents_reader_data; + PurpleHttpContentWriter response_writer; + gpointer response_writer_data; + + int timeout; + int max_redirects; + gboolean http11; + guint max_length; +}; + +struct _PurpleHttpConnection +{ + PurpleConnection *gc; + PurpleHttpCallback callback; + gpointer user_data; + gboolean is_reading; + gboolean is_keepalive; + gboolean is_cancelling; + + PurpleHttpURL *url; + PurpleHttpRequest *request; + PurpleHttpResponse *response; + + PurpleHttpKeepaliveRequest *socket_request; + PurpleHttpConnectionSet *connection_set; + PurpleHttpSocket *socket; + GString *request_header; + guint request_header_written, request_contents_written; + gboolean main_header_got, headers_got; + GString *response_buffer; + PurpleHttpGzStream *gz_stream; + + GString *contents_reader_buffer; + gboolean contents_reader_requested; + + int redirects_count; + + int length_expected; + guint length_got, length_got_decompressed; + + gboolean is_chunked, in_chunk, chunks_done; + int chunk_length, chunk_got; + + GList *link_global, *link_gc; + + guint timeout_handle; + + PurpleHttpProgressWatcher watcher; + gpointer watcher_user_data; + guint watcher_interval_threshold; + gint64 watcher_last_call; + guint watcher_delayed_handle; +}; + +struct _PurpleHttpResponse +{ + int code; + gchar *error; + + GString *contents; + PurpleHttpHeaders *headers; +}; + +struct _PurpleHttpURL +{ + gchar *protocol; + gchar *username; + gchar *password; + gchar *host; + int port; + gchar *path; + gchar *fragment; +}; + +struct _PurpleHttpHeaders +{ + GList *list; + GHashTable *by_name; +}; + +typedef struct +{ + time_t expires; + gchar *value; +} PurpleHttpCookie; + +struct _PurpleHttpCookieJar +{ + int ref_count; + + GHashTable *tab; +}; + +struct _PurpleHttpKeepaliveRequest +{ + PurpleConnection *gc; + PurpleSocketConnectCb cb; + gpointer user_data; + + PurpleHttpKeepaliveHost *host; + PurpleHttpSocket *hs; +}; + +struct _PurpleHttpKeepaliveHost +{ + PurpleHttpKeepalivePool *pool; + + gchar *host; + int port; + gboolean is_ssl; + + GSList *sockets; /* list of PurpleHttpSocket */ + + GSList *queue; /* list of PurpleHttpKeepaliveRequest */ + guint process_queue_timeout; +}; + +struct _PurpleHttpKeepalivePool +{ + gboolean is_destroying; + + int ref_count; + + guint limit_per_host; + + /* key: purple_http_socket_hash, value: PurpleHttpKeepaliveHost */ + GHashTable *by_hash; +}; + +struct _PurpleHttpConnectionSet +{ + gboolean is_destroying; + + GHashTable *connections; +}; + +struct _PurpleHttpGzStream +{ + gboolean failed; + z_stream zs; + gsize max_output; + gsize decompressed; + GString *pending; +}; + +struct _ntlm_type1_message { + guint8 protocol[8]; /* 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' */ + guint32 type; /* 0x00000001 */ + guint32 flags; /* 0x0000b203 */ + + guint16 dom_len1; /* domain string length */ + guint16 dom_len2; /* domain string length */ + guint32 dom_off; /* domain string offset */ + + guint16 host_len1; /* host string length */ + guint16 host_len2; /* host string length */ + guint32 host_off; /* host string offset (always 0x00000020) */ + +#if 0 + guint8 host[*]; /* host string (ASCII) */ + guint8 dom[*]; /* domain string (ASCII) */ +#endif +}; + +static time_t purple_http_rfc1123_to_time(const gchar *str); + +static gboolean purple_http_request_is_method(PurpleHttpRequest *request, + const gchar *method); + +static PurpleHttpConnection * purple_http_connection_new( + PurpleHttpRequest *request, PurpleConnection *gc); +static void purple_http_connection_terminate(PurpleHttpConnection *hc); +static void purple_http_conn_notify_progress_watcher(PurpleHttpConnection *hc); +static void +purple_http_conn_retry(PurpleHttpConnection *http_conn); + +static PurpleHttpResponse * purple_http_response_new(void); +static void purple_http_response_free(PurpleHttpResponse *response); + +static void purple_http_cookie_jar_parse(PurpleHttpCookieJar *cookie_jar, + GList *values); +static gchar * purple_http_cookie_jar_gen(PurpleHttpCookieJar *cookie_jar); +gchar * purple_http_cookie_jar_dump(PurpleHttpCookieJar *cjar); + +static PurpleHttpKeepaliveRequest * +purple_http_keepalive_pool_request(PurpleHttpKeepalivePool *pool, + PurpleConnection *gc, const gchar *host, int port, gboolean is_ssl, + PurpleSocketConnectCb cb, gpointer user_data); +static void +purple_http_keepalive_pool_request_cancel(PurpleHttpKeepaliveRequest *req); +static void +purple_http_keepalive_pool_release(PurpleHttpSocket *hs, gboolean invalidate); + +static void +purple_http_connection_set_remove(PurpleHttpConnectionSet *set, + PurpleHttpConnection *http_conn); + +static GRegex *purple_http_re_url, *purple_http_re_url_host, + *purple_http_re_rfc1123; + +/* + * Values: pointers to running PurpleHttpConnection. + */ +static GList *purple_http_hc_list; + +/* + * Keys: pointers to PurpleConnection. + * Values: GList of pointers to running PurpleHttpConnection. + */ +static GHashTable *purple_http_hc_by_gc; + +/* + * Keys: pointers to PurpleConnection. + * Values: gboolean TRUE. + */ +static GHashTable *purple_http_cancelling_gc; + +/* + * Keys: pointers to PurpleHttpConnection. + * Values: pointers to links in purple_http_hc_list. + */ +static GHashTable *purple_http_hc_by_ptr; + +/*** Helper functions *********************************************************/ + +static time_t purple_http_rfc1123_to_time(const gchar *str) +{ + static const gchar *months[13] = { + "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec", NULL + }; + GMatchInfo *match_info; + gchar *d_date, *d_month, *d_year, *d_time; + int month; + gchar *iso_date; + time_t t; + + g_return_val_if_fail(str != NULL, 0); + + g_regex_match(purple_http_re_rfc1123, str, 0, &match_info); + if (!g_match_info_matches(match_info)) { + g_match_info_free(match_info); + return 0; + } + + d_date = g_match_info_fetch(match_info, 1); + d_month = g_match_info_fetch(match_info, 2); + d_year = g_match_info_fetch(match_info, 3); + d_time = g_match_info_fetch(match_info, 4); + + g_match_info_free(match_info); + + month = 0; + while (months[month] != NULL) { + if (0 == g_ascii_strcasecmp(d_month, months[month])) + break; + month++; + } + month++; + + iso_date = g_strdup_printf("%s-%02d-%sT%s+00:00", + d_year, month, d_date, d_time); + + g_free(d_date); + g_free(d_month); + g_free(d_year); + g_free(d_time); + + if (month > 12) { + purple_debug_warning("http", "Invalid month: %s\n", d_month); + g_free(iso_date); + return 0; + } + + t = purple_str_to_time(iso_date, TRUE, NULL, NULL, NULL); + + g_free(iso_date); + + return t; +} + +/*** GZip streams *************************************************************/ + +static PurpleHttpGzStream * +purple_http_gz_new(gsize max_output, gboolean is_deflate) +{ + PurpleHttpGzStream *gzs = g_new0(PurpleHttpGzStream, 1); + int windowBits; + + if (is_deflate) + windowBits = -MAX_WBITS; + else /* is gzip */ + windowBits = MAX_WBITS + 32; + + if (inflateInit2(&gzs->zs, windowBits) != Z_OK) { + purple_debug_error("http", "Cannot initialize zlib stream\n"); + g_free(gzs); + return NULL; + } + + gzs->max_output = max_output; + + return gzs; +} + +static GString * +purple_http_gz_put(PurpleHttpGzStream *gzs, const gchar *buf, gsize len) +{ + const gchar *compressed_buff; + gsize compressed_len; + GString *ret; + z_stream *zs; + + g_return_val_if_fail(gzs != NULL, NULL); + g_return_val_if_fail(buf != NULL, NULL); + + if (gzs->failed) + return NULL; + + zs = &gzs->zs; + + if (gzs->pending) { + g_string_append_len(gzs->pending, buf, len); + compressed_buff = gzs->pending->str; + compressed_len = gzs->pending->len; + } else { + compressed_buff = buf; + compressed_len = len; + } + + zs->next_in = (z_const Bytef*)compressed_buff; + zs->avail_in = compressed_len; + + ret = g_string_new(NULL); + while (zs->avail_in > 0) { + int gzres; + gchar decompressed_buff[PURPLE_HTTP_GZ_BUFF_LEN]; + gsize decompressed_len; + + zs->next_out = (Bytef*)decompressed_buff; + zs->avail_out = sizeof(decompressed_buff); + decompressed_len = zs->avail_out = sizeof(decompressed_buff); + gzres = inflate(zs, Z_FULL_FLUSH); + decompressed_len -= zs->avail_out; + + if (gzres == Z_OK || gzres == Z_STREAM_END) { + if (decompressed_len == 0) + break; + if (gzs->decompressed + decompressed_len >= + gzs->max_output) + { + purple_debug_warning("http", "Maximum amount of" + " decompressed data is reached\n"); + decompressed_len = gzs->max_output - + gzs->decompressed; + gzres = Z_STREAM_END; + } + gzs->decompressed += decompressed_len; + g_string_append_len(ret, decompressed_buff, + decompressed_len); + if (gzres == Z_STREAM_END) + break; + } else { + purple_debug_error("http", + "Decompression failed (%d): %s\n", gzres, + zs->msg); + gzs->failed = TRUE; + return NULL; + } + } + + if (gzs->pending) { + g_string_free(gzs->pending, TRUE); + gzs->pending = NULL; + } + + if (zs->avail_in > 0) { + gzs->pending = g_string_new_len((gchar*)zs->next_in, + zs->avail_in); + } + + return ret; +} + +static void +purple_http_gz_free(PurpleHttpGzStream *gzs) +{ + if (gzs == NULL) + return; + inflateEnd(&gzs->zs); + if (gzs->pending) + g_string_free(gzs->pending, TRUE); + g_free(gzs); +} + +/*** NTLM *********************************************************************/ + +/** + * purple_ntlm_gen_type1: + * @hostname: Your hostname + * @domain: The domain to authenticate to + * + * Generates the base64 encoded type 1 message needed for NTLM authentication + * + * Returns: base64 encoded string to send to the server. This should + * be g_free'd by the caller. + */ +static gchar * +purple_http_ntlm_gen_type1(const gchar *hostname, const gchar *domain) +{ + int hostnamelen,host_off; + int domainlen,dom_off; + unsigned char *msg; + struct _ntlm_type1_message *tmsg; + gchar *tmp; + + hostnamelen = strlen(hostname); + domainlen = strlen(domain); + host_off = sizeof(struct _ntlm_type1_message); + dom_off = sizeof(struct _ntlm_type1_message) + hostnamelen; + msg = g_malloc0(sizeof(struct _ntlm_type1_message) + hostnamelen + domainlen); + tmsg = (struct _ntlm_type1_message*)(gpointer)msg; + tmsg->protocol[0] = 'N'; + tmsg->protocol[1] = 'T'; + tmsg->protocol[2] = 'L'; + tmsg->protocol[3] = 'M'; + tmsg->protocol[4] = 'S'; + tmsg->protocol[5] = 'S'; + tmsg->protocol[6] = 'P'; + tmsg->protocol[7] = '\0'; + tmsg->type = GUINT32_TO_LE(0x00000001); + tmsg->flags = GUINT32_TO_LE(0x0000b203); + tmsg->dom_len1 = tmsg->dom_len2 = GUINT16_TO_LE(domainlen); + tmsg->dom_off = GUINT32_TO_LE(dom_off); + tmsg->host_len1 = tmsg->host_len2 = GUINT16_TO_LE(hostnamelen); + tmsg->host_off = GUINT32_TO_LE(host_off); + memcpy(msg + host_off, hostname, hostnamelen); + memcpy(msg + dom_off, domain, domainlen); + + tmp = g_base64_encode(msg, sizeof(struct _ntlm_type1_message) + hostnamelen + domainlen); + g_free(msg); + + return tmp; +} + +/*** HTTP Sockets *************************************************************/ + +static gchar * +purple_http_socket_hash(const gchar *host, int port, gboolean is_ssl) +{ + return g_strdup_printf("%c:%s:%d", (is_ssl ? 'S' : 'R'), host, port); +} + +static PurpleHttpSocket * +purple_http_socket_connect_new(PurpleConnection *gc, const gchar *host, + int port, gboolean is_ssl, PurpleSocketConnectCb cb, gpointer user_data) +{ + PurpleHttpSocket *hs = g_new0(PurpleHttpSocket, 1); + + hs->ps = purple_socket_new(gc); + purple_socket_set_data(hs->ps, "hs", hs); + purple_socket_set_tls(hs->ps, is_ssl); + purple_socket_set_host(hs->ps, host); + purple_socket_set_port(hs->ps, port); + if (!purple_socket_connect(hs->ps, cb, user_data)) { + purple_socket_destroy(hs->ps); + g_free(hs); + return NULL; + } + + if (purple_debug_is_verbose()) + purple_debug_misc("http", "new socket created: %p\n", hs); + + return hs; +} + +static void +purple_http_socket_close_free(PurpleHttpSocket *hs) +{ + if (hs == NULL) + return; + + if (purple_debug_is_verbose()) + purple_debug_misc("http", "destroying socket: %p\n", hs); + + purple_socket_destroy(hs->ps); + g_free(hs); +} + +/*** Headers collection *******************************************************/ + +static PurpleHttpHeaders * purple_http_headers_new(void); +static void purple_http_headers_free(PurpleHttpHeaders *hdrs); +static void purple_http_headers_add(PurpleHttpHeaders *hdrs, const gchar *key, + const gchar *value); +static const GList * purple_http_headers_get_all(PurpleHttpHeaders *hdrs); +static GList * purple_http_headers_get_all_by_name( + PurpleHttpHeaders *hdrs, const gchar *key); +static const gchar * purple_http_headers_get(PurpleHttpHeaders *hdrs, + const gchar *key); +static gboolean purple_http_headers_get_int(PurpleHttpHeaders *hdrs, + const gchar *key, int *dst); +static gboolean purple_http_headers_match(PurpleHttpHeaders *hdrs, + const gchar *key, const gchar *value); +static gchar * purple_http_headers_dump(PurpleHttpHeaders *hdrs); + +static PurpleHttpHeaders * purple_http_headers_new(void) +{ + PurpleHttpHeaders *hdrs = g_new0(PurpleHttpHeaders, 1); + + hdrs->by_name = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)g_list_free); + + return hdrs; +} + +static void purple_http_headers_free_kvp(PurpleKeyValuePair *kvp) +{ + g_free(kvp->key); + g_free(kvp->value); + g_free(kvp); +} + +static void purple_http_headers_free(PurpleHttpHeaders *hdrs) +{ + if (hdrs == NULL) + return; + + g_hash_table_destroy(hdrs->by_name); + g_list_free_full(hdrs->list, + (GDestroyNotify)purple_http_headers_free_kvp); + g_free(hdrs); +} + +static void purple_http_headers_add(PurpleHttpHeaders *hdrs, const gchar *key, + const gchar *value) +{ + PurpleKeyValuePair *kvp; + GList *named_values, *new_values; + gchar *key_low; + + g_return_if_fail(hdrs != NULL); + g_return_if_fail(key != NULL); + g_return_if_fail(value != NULL); + + kvp = g_new0(PurpleKeyValuePair, 1); + kvp->key = g_strdup(key); + kvp->value = g_strdup(value); + hdrs->list = g_list_append(hdrs->list, kvp); + + key_low = g_ascii_strdown(key, -1); + named_values = g_hash_table_lookup(hdrs->by_name, key_low); + new_values = g_list_append(named_values, kvp->value); + if (named_values) + g_free(key_low); + else + g_hash_table_insert(hdrs->by_name, key_low, new_values); +} + +static void purple_http_headers_remove(PurpleHttpHeaders *hdrs, + const gchar *key) +{ + GList *it, *curr; + + g_return_if_fail(hdrs != NULL); + g_return_if_fail(key != NULL); + + if (!g_hash_table_remove(hdrs->by_name, key)) + return; + + /* Could be optimized to O(1). */ + it = g_list_first(hdrs->list); + while (it) { + PurpleKeyValuePair *kvp = it->data; + curr = it; + it = g_list_next(it); + if (g_ascii_strcasecmp(kvp->key, key) != 0) + continue; + + hdrs->list = g_list_delete_link(hdrs->list, curr); + purple_http_headers_free_kvp(kvp); + } +} + +static const GList * purple_http_headers_get_all(PurpleHttpHeaders *hdrs) +{ + g_return_val_if_fail(hdrs != NULL, NULL); + + return hdrs->list; +} + +/* return const */ +static GList * purple_http_headers_get_all_by_name( + PurpleHttpHeaders *hdrs, const gchar *key) +{ + GList *values; + gchar *key_low; + + g_return_val_if_fail(hdrs != NULL, NULL); + g_return_val_if_fail(key != NULL, NULL); + + key_low = g_ascii_strdown(key, -1); + values = g_hash_table_lookup(hdrs->by_name, key_low); + g_free(key_low); + + return values; +} + +static const gchar * purple_http_headers_get(PurpleHttpHeaders *hdrs, + const gchar *key) +{ + const GList *values = purple_http_headers_get_all_by_name(hdrs, key); + + if (!values) + return NULL; + + return values->data; +} + +static gboolean purple_http_headers_get_int(PurpleHttpHeaders *hdrs, + const gchar *key, int *dst) +{ + int val; + const gchar *str; + + str = purple_http_headers_get(hdrs, key); + if (!str) + return FALSE; + + if (1 != sscanf(str, "%d", &val)) + return FALSE; + + *dst = val; + return TRUE; +} + +static gboolean purple_http_headers_match(PurpleHttpHeaders *hdrs, + const gchar *key, const gchar *value) +{ + const gchar *str; + + str = purple_http_headers_get(hdrs, key); + if (str == NULL || value == NULL) + return str == value; + + return (g_ascii_strcasecmp(str, value) == 0); +} + +static gchar * purple_http_headers_dump(PurpleHttpHeaders *hdrs) +{ + const GList *hdr; + + GString *s = g_string_new(""); + + hdr = purple_http_headers_get_all(hdrs); + while (hdr) { + PurpleKeyValuePair *kvp = hdr->data; + hdr = g_list_next(hdr); + + g_string_append_printf(s, "%s: %s%s", kvp->key, + (gchar*)kvp->value, hdr ? "\n" : ""); + } + + return g_string_free(s, FALSE); +} + +/*** HTTP protocol backend ****************************************************/ + +static void _purple_http_disconnect(PurpleHttpConnection *hc, + gboolean is_graceful); + +static void _purple_http_gen_headers(PurpleHttpConnection *hc); +static gboolean _purple_http_recv_loopbody(PurpleHttpConnection *hc, gint fd); +static void _purple_http_recv(gpointer _hc, gint fd, + PurpleInputCondition cond); +static void _purple_http_send(gpointer _hc, gint fd, PurpleInputCondition cond); + +/* closes current connection (if exists), estabilishes one and proceeds with + * request */ +static gboolean _purple_http_reconnect(PurpleHttpConnection *hc); + +static void _purple_http_error(PurpleHttpConnection *hc, const char *format, + ...) G_GNUC_PRINTF(2, 3); + +static void _purple_http_error(PurpleHttpConnection *hc, const char *format, + ...) +{ + va_list args; + + va_start(args, format); + hc->response->error = g_strdup_vprintf(format, args); + va_end(args); + + if (purple_debug_is_verbose()) + purple_debug_warning("http", "error: %s\n", hc->response->error); + + purple_http_conn_cancel(hc); +} + +static void _purple_http_gen_headers(PurpleHttpConnection *hc) +{ + GString *h; + PurpleHttpURL *url; + const GList *hdr; + PurpleHttpRequest *req; + PurpleHttpHeaders *hdrs; + gchar *request_url, *tmp_url = NULL; + + PurpleProxyInfo *proxy; + gboolean proxy_http = FALSE; + const gchar *proxy_username, *proxy_password; + + g_return_if_fail(hc != NULL); + + if (hc->request_header != NULL) + return; + + req = hc->request; + url = hc->url; + hdrs = req->headers; + proxy = purple_proxy_get_setup(hc->gc ? + purple_connection_get_account(hc->gc) : NULL); + + proxy_http = (purple_proxy_info_get_proxy_type(proxy) == PURPLE_PROXY_HTTP || + purple_proxy_info_get_proxy_type(proxy) == PURPLE_PROXY_USE_ENVVAR); + /* this is HTTP proxy, but used with tunelling with CONNECT */ + if (proxy_http && url->port != 80) + proxy_http = FALSE; + + hc->request_header = h = g_string_new(""); + hc->request_header_written = 0; + hc->request_contents_written = 0; + + if (proxy_http) + request_url = tmp_url = purple_http_url_print(url); + else + request_url = url->path; + + g_string_append_printf(h, "%s %s HTTP/%s\r\n", + req->method ? req->method : "GET", + request_url, + req->http11 ? "1.1" : "1.0"); + + g_free(tmp_url); + + if (!purple_http_headers_get(hdrs, "host")) + g_string_append_printf(h, "Host: %s\r\n", url->host); + if (!purple_http_headers_get(hdrs, "connection")) { + g_string_append(h, "Connection: "); + g_string_append(h, hc->is_keepalive ? + "Keep-Alive\r\n" : "close\r\n"); + } + if (!purple_http_headers_get(hdrs, "accept")) + g_string_append(h, "Accept: */*\r\n"); + if (!purple_http_headers_get(hdrs, "accept-encoding")) + g_string_append(h, "Accept-Encoding: gzip, deflate\r\n"); + + if (!purple_http_headers_get(hdrs, "content-length") && ( + req->contents_length > 0 || + purple_http_request_is_method(req, "post"))) + { + g_string_append_printf(h, "Content-Length: %u\r\n", + req->contents_length); + } + + if (proxy_http) + g_string_append(h, "Proxy-Connection: close\r\n"); /* TEST: proxy+KeepAlive */ + + proxy_username = purple_proxy_info_get_username(proxy); + if (proxy_http && proxy_username != NULL && proxy_username[0] != '\0') { + gchar *proxy_auth, *ntlm_type1, *tmp; + int len; + + proxy_password = purple_proxy_info_get_password(proxy); + if (proxy_password == NULL) + proxy_password = ""; + + tmp = g_strdup_printf("%s:%s", proxy_username, proxy_password); + len = strlen(tmp); + proxy_auth = g_base64_encode((const guchar *)tmp, len); + memset(tmp, 0, len); + g_free(tmp); + + ntlm_type1 = purple_http_ntlm_gen_type1(purple_get_host_name(), + ""); + + g_string_append_printf(h, "Proxy-Authorization: Basic %s\r\n", + proxy_auth); + g_string_append_printf(h, "Proxy-Authorization: NTLM %s\r\n", + ntlm_type1); + g_string_append(h, "Proxy-Connection: close\r\n"); /* TEST: proxy+KeepAlive */ + + memset(proxy_auth, 0, strlen(proxy_auth)); + g_free(proxy_auth); + g_free(ntlm_type1); + } + + hdr = purple_http_headers_get_all(hdrs); + while (hdr) { + PurpleKeyValuePair *kvp = hdr->data; + hdr = g_list_next(hdr); + + g_string_append_printf(h, "%s: %s\r\n", + kvp->key, (gchar*)kvp->value); + } + + if (!purple_http_cookie_jar_is_empty(req->cookie_jar)) { + gchar * cookies = purple_http_cookie_jar_gen(req->cookie_jar); + g_string_append_printf(h, "Cookie: %s\r\n", cookies); + g_free(cookies); + } + + g_string_append_printf(h, "\r\n"); + + if (purple_debug_is_unsafe() && purple_debug_is_verbose()) { + purple_debug_misc("http", "Generated request headers:\n%s", + h->str); + } +} + +static gboolean _purple_http_recv_headers(PurpleHttpConnection *hc, + const gchar *buf, int len) +{ + gchar *eol, *delim; + + if (hc->headers_got) { + purple_debug_error("http", "Headers already got\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + return FALSE; + } + + g_string_append_len(hc->response_buffer, buf, len); + if (hc->response_buffer->len > PURPLE_HTTP_MAX_RECV_BUFFER_LEN) { + purple_debug_error("http", + "Buffer too big when parsing headers\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + return FALSE; + } + + while ((eol = strstr(hc->response_buffer->str, "\r\n")) + != NULL) + { + gchar *hdrline = hc->response_buffer->str; + int hdrline_len = eol - hdrline; + + hdrline[hdrline_len] = '\0'; + + if (hdrline[0] == '\0') { + if (!hc->main_header_got) { + if (purple_debug_is_verbose() && + hc->is_keepalive) + { + purple_debug_misc("http", "Got keep-" + "alive terminator from previous" + " request\n"); + } else { + purple_debug_warning("http", "Got empty" + " line at the beginning - this " + "may be a HTTP server quirk\n"); + } + } else /* hc->main_header_got */ { + hc->headers_got = TRUE; + if (purple_debug_is_verbose()) { + purple_debug_misc("http", "Got headers " + "end\n"); + } + } + } else if (!hc->main_header_got) { + hc->main_header_got = TRUE; + delim = strchr(hdrline, ' '); + if (delim == NULL || 1 != sscanf(delim + 1, "%d", + &hc->response->code)) + { + purple_debug_warning("http", + "Invalid response code\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + return FALSE; + } + if (purple_debug_is_verbose()) + purple_debug_misc("http", + "Got main header with code %d\n", + hc->response->code); + } else { + if (purple_debug_is_verbose() && + purple_debug_is_unsafe()) + purple_debug_misc("http", "Got header: %s\n", + hdrline); + delim = strchr(hdrline, ':'); + if (delim == NULL || delim == hdrline) { + purple_debug_warning("http", + "Bad header delimiter\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + return FALSE; + } + *delim++ = '\0'; + while (*delim == ' ') + delim++; + + purple_http_headers_add(hc->response->headers, hdrline, delim); + } + + g_string_erase(hc->response_buffer, 0, hdrline_len + 2); + if (hc->headers_got) + break; + } + return TRUE; +} + +static gboolean _purple_http_recv_body_data(PurpleHttpConnection *hc, + const gchar *buf, int len) +{ + GString *decompressed = NULL; + + if (hc->length_expected >= 0 && + len + hc->length_got > (guint)hc->length_expected) + { + len = hc->length_expected - hc->length_got; + } + + hc->length_got += len; + + if (hc->gz_stream != NULL) { + decompressed = purple_http_gz_put(hc->gz_stream, buf, len); + if (decompressed == NULL) { + _purple_http_error(hc, + _("Error while decompressing data")); + return FALSE; + } + buf = decompressed->str; + len = decompressed->len; + } + + g_assert(hc->request->max_length <= + PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH); + if (hc->length_got_decompressed + len > hc->request->max_length) { + purple_debug_warning("http", + "Maximum length exceeded, truncating\n"); + len = hc->request->max_length - hc->length_got_decompressed; + hc->length_expected = hc->length_got; + } + hc->length_got_decompressed += len; + + if (len == 0) { + if (decompressed != NULL) + g_string_free(decompressed, TRUE); + return TRUE; + } + + if (hc->request->response_writer != NULL) { + gboolean succ; + succ = hc->request->response_writer(hc, hc->response, buf, + hc->length_got_decompressed, len, + hc->request->response_writer_data); + if (!succ) { + if (decompressed != NULL) + g_string_free(decompressed, TRUE); + purple_debug_error("http", + "Cannot write using callback\n"); + _purple_http_error(hc, + _("Error handling retrieved data")); + return FALSE; + } + } else { + if (hc->response->contents == NULL) + hc->response->contents = g_string_new(""); + g_string_append_len(hc->response->contents, buf, len); + } + + if (decompressed != NULL) + g_string_free(decompressed, TRUE); + + purple_http_conn_notify_progress_watcher(hc); + return TRUE; +} + +static gboolean _purple_http_recv_body_chunked(PurpleHttpConnection *hc, + const gchar *buf, int len) +{ + gchar *eol, *line; + int line_len; + + if (hc->chunks_done) + return FALSE; + if (!hc->response_buffer) + hc->response_buffer = g_string_new(""); + + g_string_append_len(hc->response_buffer, buf, len); + if (hc->response_buffer->len > PURPLE_HTTP_MAX_RECV_BUFFER_LEN) { + purple_debug_error("http", + "Buffer too big when searching for chunk\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + return FALSE; + } + + while (hc->response_buffer->len > 0) { + if (hc->in_chunk) { + int got_now = hc->response_buffer->len; + if (hc->chunk_got + got_now > hc->chunk_length) + got_now = hc->chunk_length - hc->chunk_got; + hc->chunk_got += got_now; + + if (!_purple_http_recv_body_data(hc, + hc->response_buffer->str, got_now)) + return FALSE; + + g_string_erase(hc->response_buffer, 0, got_now); + hc->in_chunk = (hc->chunk_got < hc->chunk_length); + + continue; + } + + line = hc->response_buffer->str; + eol = strstr(line, "\r\n"); + if (eol == line) { + g_string_erase(hc->response_buffer, 0, 2); + line = hc->response_buffer->str; + eol = strstr(line, "\r\n"); + } + if (eol == NULL) { + /* waiting for more data (unlikely, but possible) */ + if (hc->response_buffer->len > 20) { + purple_debug_warning("http", "Chunk length not " + "found (buffer too large)\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + return FALSE; + } + return TRUE; + } + line_len = eol - line; + + if (1 != sscanf(line, "%x", &hc->chunk_length)) { + if (purple_debug_is_unsafe()) + purple_debug_warning("http", + "Chunk length not found in [%s]\n", + line); + else + purple_debug_warning("http", + "Chunk length not found\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + return FALSE; + } + hc->chunk_got = 0; + hc->in_chunk = TRUE; + + if (purple_debug_is_verbose()) + purple_debug_misc("http", "Found chunk of length %d\n", hc->chunk_length); + + g_string_erase(hc->response_buffer, 0, line_len + 2); + + if (hc->chunk_length == 0) { + hc->chunks_done = TRUE; + hc->in_chunk = FALSE; + return TRUE; + } + } + + return TRUE; +} + +static gboolean _purple_http_recv_body(PurpleHttpConnection *hc, + const gchar *buf, int len) +{ + if (hc->is_chunked) + return _purple_http_recv_body_chunked(hc, buf, len); + + return _purple_http_recv_body_data(hc, buf, len); +} + +static gboolean _purple_http_recv_loopbody(PurpleHttpConnection *hc, gint fd) +{ + int len; + gchar buf[4096]; + gboolean got_anything; + + len = purple_socket_read(hc->socket->ps, (guchar*)buf, sizeof(buf)); + got_anything = (len > 0); + + if (len < 0 && errno == EAGAIN) + return FALSE; + + if (len < 0) { + _purple_http_error(hc, _("Error reading from %s: %s"), + hc->url->host, g_strerror(errno)); + return FALSE; + } + + /* EOF */ + if (len == 0) { + if (hc->request->max_length == 0) { + /* It's definitely YHttpServer quirk. */ + purple_debug_warning("http", "Got EOF, but no data was " + "expected (this may be a server quirk)\n"); + hc->length_expected = hc->length_got; + } + if (hc->length_expected >= 0 && + hc->length_got < (guint)hc->length_expected) + { + purple_debug_warning("http", "No more data while reading" + " contents\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + return FALSE; + } + if (hc->headers_got) + hc->length_expected = hc->length_got; + else if (hc->length_got == 0 && hc->socket->use_count > 1) { + purple_debug_info("http", "Keep-alive connection " + "expired (when reading), retrying...\n"); + purple_http_conn_retry(hc); + return FALSE; + } else { + const gchar *server = purple_http_headers_get( + hc->response->headers, "Server"); + if (server && + g_ascii_strcasecmp(server, "YHttpServer") == 0) + { + purple_debug_warning("http", "No more data " + "while parsing headers (YHttpServer " + "quirk)\n"); + hc->headers_got = TRUE; + hc->length_expected = hc->length_got = 0; + hc->length_got_decompressed = 0; + } else { + purple_debug_warning("http", "No more data " + "while parsing headers\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + return FALSE; + } + } + } + + if (!hc->headers_got && len > 0) { + if (!_purple_http_recv_headers(hc, buf, len)) + return FALSE; + len = 0; + if (hc->headers_got) { + gboolean is_gzip, is_deflate; + if (!purple_http_headers_get_int(hc->response->headers, + "Content-Length", &hc->length_expected)) + hc->length_expected = -1; + hc->is_chunked = (purple_http_headers_match( + hc->response->headers, + "Transfer-Encoding", "chunked")); + is_gzip = purple_http_headers_match( + hc->response->headers, "Content-Encoding", + "gzip"); + is_deflate = purple_http_headers_match( + hc->response->headers, "Content-Encoding", + "deflate"); + if (is_gzip || is_deflate) { + hc->gz_stream = purple_http_gz_new( + hc->request->max_length + 1, + is_deflate); + } + } + if (hc->headers_got && hc->response_buffer && + hc->response_buffer->len > 0) + { + int buffer_len = hc->response_buffer->len; + gchar *buffer = g_string_free(hc->response_buffer, FALSE); + hc->response_buffer = NULL; + _purple_http_recv_body(hc, buffer, buffer_len); + g_free(buffer); + } + if (!hc->headers_got) + return got_anything; + } + + if (len > 0) { + if (!_purple_http_recv_body(hc, buf, len)) + return FALSE; + } + + if (hc->is_chunked && hc->chunks_done && hc->length_expected < 0) + hc->length_expected = hc->length_got; + + if (hc->length_expected >= 0 && + hc->length_got >= (guint)hc->length_expected) + { + const gchar *redirect; + + if (hc->is_chunked && !hc->chunks_done) { + if (len == 0) { + _purple_http_error(hc, _("Chunked connection terminated")); + return FALSE; + } + if (purple_debug_is_verbose()) { + purple_debug_misc("http", + "I need the terminating empty chunk\n"); + } + return TRUE; + } + + if (!hc->headers_got) { + hc->response->code = 0; + purple_debug_warning("http", "No headers got\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + return FALSE; + } + + if (purple_debug_is_unsafe() && purple_debug_is_verbose()) { + gchar *hdrs = purple_http_headers_dump( + hc->response->headers); + purple_debug_misc("http", "Got response headers: %s\n", + hdrs); + g_free(hdrs); + } + + purple_http_cookie_jar_parse(hc->request->cookie_jar, + purple_http_headers_get_all_by_name( + hc->response->headers, "Set-Cookie")); + + if (purple_debug_is_unsafe() && purple_debug_is_verbose() && + !purple_http_cookie_jar_is_empty( + hc->request->cookie_jar)) + { + gchar *cookies = purple_http_cookie_jar_dump( + hc->request->cookie_jar); + purple_debug_misc("http", "Cookies: %s\n", cookies); + g_free(cookies); + } + + if (hc->response->code == 407) { + _purple_http_error(hc, _("Invalid proxy credentials")); + return FALSE; + } + + redirect = purple_http_headers_get(hc->response->headers, + "location"); + if (redirect && (hc->request->max_redirects == -1 || + hc->request->max_redirects > hc->redirects_count)) + { + PurpleHttpURL *url = purple_http_url_parse(redirect); + + hc->redirects_count++; + + if (!url) { + if (purple_debug_is_unsafe()) + purple_debug_warning("http", + "Invalid redirect to %s\n", + redirect); + else + purple_debug_warning("http", + "Invalid redirect\n"); + _purple_http_error(hc, _("Error parsing HTTP")); + } + + purple_http_url_relative(hc->url, url); + purple_http_url_free(url); + + _purple_http_reconnect(hc); + return FALSE; + } + + _purple_http_disconnect(hc, TRUE); + purple_http_connection_terminate(hc); + return FALSE; + } + + return got_anything; +} + +static void _purple_http_recv(gpointer _hc, gint fd, PurpleInputCondition cond) +{ + PurpleHttpConnection *hc = _hc; + + while (_purple_http_recv_loopbody(hc, fd)); +} + +static void _purple_http_send_got_data(PurpleHttpConnection *hc, + gboolean success, gboolean eof, size_t stored) +{ + int estimated_length; + + g_return_if_fail(hc != NULL); + + if (!success) { + _purple_http_error(hc, _("Error requesting data to write")); + return; + } + + hc->contents_reader_requested = FALSE; + g_string_set_size(hc->contents_reader_buffer, stored); + if (!eof) + return; + + estimated_length = hc->request_contents_written + stored; + + if (hc->request->contents_length != -1 && + hc->request->contents_length != estimated_length) + { + purple_debug_warning("http", + "Invalid amount of data has been written\n"); + } + hc->request->contents_length = estimated_length; +} + +static void _purple_http_send(gpointer _hc, gint fd, PurpleInputCondition cond) +{ + PurpleHttpConnection *hc = _hc; + int written, write_len; + const gchar *write_from; + gboolean writing_headers; + + /* Waiting for data. This could be written more efficiently, by removing + * (and later, adding) hs->inpa. */ + if (hc->contents_reader_requested) + return; + + _purple_http_gen_headers(hc); + + writing_headers = + (hc->request_header_written < hc->request_header->len); + if (writing_headers) { + write_from = hc->request_header->str + + hc->request_header_written; + write_len = hc->request_header->len - + hc->request_header_written; + } else if (hc->request->contents_reader) { + if (hc->contents_reader_requested) + return; /* waiting for data */ + if (!hc->contents_reader_buffer) + hc->contents_reader_buffer = g_string_new(""); + if (hc->contents_reader_buffer->len == 0) { + hc->contents_reader_requested = TRUE; + g_string_set_size(hc->contents_reader_buffer, + PURPLE_HTTP_MAX_READ_BUFFER_LEN); + hc->request->contents_reader(hc, + hc->contents_reader_buffer->str, + hc->request_contents_written, + PURPLE_HTTP_MAX_READ_BUFFER_LEN, + hc->request->contents_reader_data, + _purple_http_send_got_data); + return; + } + write_from = hc->contents_reader_buffer->str; + write_len = hc->contents_reader_buffer->len; + } else { + write_from = hc->request->contents + + hc->request_contents_written; + write_len = hc->request->contents_length - + hc->request_contents_written; + } + + if (write_len == 0) { + purple_debug_warning("http", "Nothing to write\n"); + written = 0; + } else { + written = purple_socket_write(hc->socket->ps, + (const guchar*)write_from, write_len); + } + + if (written < 0 && errno == EAGAIN) + return; + + if (written < 0) { + if (hc->request_header_written == 0 && + hc->socket->use_count > 1) + { + purple_debug_info("http", "Keep-alive connection " + "expired (when writing), retrying...\n"); + purple_http_conn_retry(hc); + return; + } + + _purple_http_error(hc, _("Error writing to %s: %s"), + hc->url->host, g_strerror(errno)); + return; + } + + if (writing_headers) { + hc->request_header_written += written; + purple_http_conn_notify_progress_watcher(hc); + if (hc->request_header_written < hc->request_header->len) + return; + if (hc->request->contents_length > 0) + return; + } else { + hc->request_contents_written += written; + purple_http_conn_notify_progress_watcher(hc); + if (hc->contents_reader_buffer) + g_string_erase(hc->contents_reader_buffer, 0, written); + if (hc->request->contents_length > 0 && + hc->request_contents_written < + (guint)hc->request->contents_length) + { + return; + } + } + + /* request is completely written, let's read the response */ + hc->is_reading = TRUE; + purple_socket_watch(hc->socket->ps, PURPLE_INPUT_READ, + _purple_http_recv, hc); +} + +static void _purple_http_disconnect(PurpleHttpConnection *hc, + gboolean is_graceful) +{ + g_return_if_fail(hc != NULL); + + if (hc->request_header) + g_string_free(hc->request_header, TRUE); + hc->request_header = NULL; + + if (hc->response_buffer) + g_string_free(hc->response_buffer, TRUE); + hc->response_buffer = NULL; + + if (hc->socket_request) + purple_http_keepalive_pool_request_cancel(hc->socket_request); + else { + purple_http_keepalive_pool_release(hc->socket, !is_graceful); + hc->socket = NULL; + } +} + +static void +_purple_http_connected(PurpleSocket *ps, const gchar *error, gpointer _hc) +{ + PurpleHttpSocket *hs = NULL; + PurpleHttpConnection *hc = _hc; + + if (ps != NULL) + hs = purple_socket_get_data(ps, "hs"); + + hc->socket_request = NULL; + hc->socket = hs; + + if (error != NULL) { + _purple_http_error(hc, _("Unable to connect to %s: %s"), + hc->url->host, error); + return; + } + + purple_socket_watch(ps, PURPLE_INPUT_WRITE, _purple_http_send, hc); +} + +static gboolean _purple_http_reconnect(PurpleHttpConnection *hc) +{ + PurpleHttpURL *url; + gboolean is_ssl = FALSE; + + g_return_val_if_fail(hc != NULL, FALSE); + g_return_val_if_fail(hc->url != NULL, FALSE); + + _purple_http_disconnect(hc, TRUE); + + if (purple_debug_is_verbose()) { + if (purple_debug_is_unsafe()) { + gchar *urlp = purple_http_url_print(hc->url); + purple_debug_misc("http", "Connecting to %s...\n", urlp); + g_free(urlp); + } else + purple_debug_misc("http", "Connecting to %s...\n", + hc->url->host); + } + + url = hc->url; + if (g_strcmp0(url->protocol, "") == 0 || + g_ascii_strcasecmp(url->protocol, "http") == 0) + { + /* do nothing */ + } else if (g_ascii_strcasecmp(url->protocol, "https") == 0) { + is_ssl = TRUE; + } else { + _purple_http_error(hc, _("Unsupported protocol: %s"), + url->protocol); + return FALSE; + } + + if (hc->request->keepalive_pool != NULL) { + hc->socket_request = purple_http_keepalive_pool_request( + hc->request->keepalive_pool, hc->gc, url->host, + url->port, is_ssl, _purple_http_connected, hc); + } else { + hc->socket = purple_http_socket_connect_new(hc->gc, url->host, + url->port, is_ssl, _purple_http_connected, hc); + } + + if (hc->socket_request == NULL && hc->socket == NULL) { + _purple_http_error(hc, _("Unable to connect to %s"), url->host); + return FALSE; + } + + purple_http_headers_free(hc->response->headers); + hc->response->headers = purple_http_headers_new(); + hc->response_buffer = g_string_new(""); + hc->main_header_got = FALSE; + hc->headers_got = FALSE; + if (hc->response->contents != NULL) + g_string_free(hc->response->contents, TRUE); + hc->response->contents = NULL; + hc->length_got = 0; + hc->length_got_decompressed = 0; + hc->length_expected = -1; + hc->is_chunked = FALSE; + hc->in_chunk = FALSE; + hc->chunks_done = FALSE; + + purple_http_conn_notify_progress_watcher(hc); + + return TRUE; +} + +/*** Performing HTTP requests *************************************************/ + +static gboolean purple_http_request_timeout(gpointer _hc) +{ + PurpleHttpConnection *hc = _hc; + + purple_debug_warning("http", "Timeout reached for request %p\n", hc); + + purple_http_conn_cancel(hc); + + return FALSE; +} + +PurpleHttpConnection * purple_http_get(PurpleConnection *gc, + PurpleHttpCallback callback, gpointer user_data, const gchar *url) +{ + PurpleHttpRequest *request; + PurpleHttpConnection *hc; + + g_return_val_if_fail(url != NULL, NULL); + + request = purple_http_request_new(url); + hc = purple_http_request(gc, request, callback, user_data); + purple_http_request_unref(request); + + return hc; +} + +PurpleHttpConnection * purple_http_get_printf(PurpleConnection *gc, + PurpleHttpCallback callback, gpointer user_data, + const gchar *format, ...) +{ + va_list args; + gchar *value; + PurpleHttpConnection *ret; + + g_return_val_if_fail(format != NULL, NULL); + + va_start(args, format); + value = g_strdup_vprintf(format, args); + va_end(args); + + ret = purple_http_get(gc, callback, user_data, value); + g_free(value); + + return ret; +} + +PurpleHttpConnection * purple_http_request(PurpleConnection *gc, + PurpleHttpRequest *request, PurpleHttpCallback callback, + gpointer user_data) +{ + PurpleHttpConnection *hc; + + g_return_val_if_fail(request != NULL, NULL); + + if (request->url == NULL) { + purple_debug_error("http", "Cannot perform new request - " + "URL is not set\n"); + return NULL; + } + + if (g_hash_table_lookup(purple_http_cancelling_gc, gc)) { + purple_debug_warning("http", "Cannot perform another HTTP " + "request while cancelling all related with this " + "PurpleConnection\n"); + return NULL; + } + + hc = purple_http_connection_new(request, gc); + hc->callback = callback; + hc->user_data = user_data; + + hc->url = purple_http_url_parse(request->url); + + if (purple_debug_is_unsafe()) + purple_debug_misc("http", "Performing new request %p for %s.\n", + hc, request->url); + else + purple_debug_misc("http", "Performing new request %p to %s.\n", + hc, hc->url ? hc->url->host : NULL); + + if (!hc->url || hc->url->host == NULL || hc->url->host[0] == '\0') { + purple_debug_error("http", "Invalid URL requested.\n"); + purple_http_connection_terminate(hc); + return NULL; + } + + _purple_http_reconnect(hc); + + hc->timeout_handle = g_timeout_add_seconds(request->timeout, + purple_http_request_timeout, hc); + + return hc; +} + +/*** HTTP connection API ******************************************************/ + +static void purple_http_connection_free(PurpleHttpConnection *hc); +static gboolean purple_http_conn_notify_progress_watcher_timeout(gpointer _hc); + +static PurpleHttpConnection * purple_http_connection_new( + PurpleHttpRequest *request, PurpleConnection *gc) +{ + PurpleHttpConnection *hc = g_new0(PurpleHttpConnection, 1); + + g_assert(request != NULL); + + hc->request = request; + purple_http_request_ref(request); + hc->response = purple_http_response_new(); + hc->is_keepalive = (request->keepalive_pool != NULL); + + hc->link_global = purple_http_hc_list = + g_list_prepend(purple_http_hc_list, hc); + g_hash_table_insert(purple_http_hc_by_ptr, hc, hc->link_global); + if (gc) { + GList *gc_list = g_hash_table_lookup(purple_http_hc_by_gc, gc); + g_hash_table_steal(purple_http_hc_by_gc, gc); + hc->link_gc = gc_list = g_list_prepend(gc_list, hc); + g_hash_table_insert(purple_http_hc_by_gc, gc, gc_list); + hc->gc = gc; + } + + return hc; +} + +static void purple_http_connection_free(PurpleHttpConnection *hc) +{ + if (hc->timeout_handle) + g_source_remove(hc->timeout_handle); + if (hc->watcher_delayed_handle) + g_source_remove(hc->watcher_delayed_handle); + + if (hc->connection_set != NULL) + purple_http_connection_set_remove(hc->connection_set, hc); + + purple_http_url_free(hc->url); + purple_http_request_unref(hc->request); + purple_http_response_free(hc->response); + + if (hc->contents_reader_buffer) + g_string_free(hc->contents_reader_buffer, TRUE); + purple_http_gz_free(hc->gz_stream); + + if (hc->request_header) + g_string_free(hc->request_header, TRUE); + + purple_http_hc_list = g_list_delete_link(purple_http_hc_list, + hc->link_global); + g_hash_table_remove(purple_http_hc_by_ptr, hc); + if (hc->gc) { + GList *gc_list, *gc_list_new; + gc_list = g_hash_table_lookup(purple_http_hc_by_gc, hc->gc); + g_assert(gc_list != NULL); + + gc_list_new = g_list_delete_link(gc_list, hc->link_gc); + if (gc_list != gc_list_new) { + g_hash_table_steal(purple_http_hc_by_gc, hc->gc); + if (gc_list_new) + g_hash_table_insert(purple_http_hc_by_gc, + hc->gc, gc_list_new); + } + } + + g_free(hc); +} + +/* call callback and do the cleanup */ +static void purple_http_connection_terminate(PurpleHttpConnection *hc) +{ + g_return_if_fail(hc != NULL); + + purple_debug_misc("http", "Request %p performed %s.\n", hc, + purple_http_response_is_successful(hc->response) ? + "successfully" : "without success"); + + if (hc->callback) + hc->callback(hc, hc->response, hc->user_data); + + purple_http_connection_free(hc); +} + +void purple_http_conn_cancel(PurpleHttpConnection *http_conn) +{ + if (http_conn == NULL) + return; + + if (http_conn->is_cancelling) + return; + http_conn->is_cancelling = TRUE; + + if (purple_debug_is_verbose()) { + purple_debug_misc("http", "Cancelling connection %p...\n", + http_conn); + } + + http_conn->response->code = 0; + _purple_http_disconnect(http_conn, FALSE); + purple_http_connection_terminate(http_conn); +} + +static void +purple_http_conn_retry(PurpleHttpConnection *http_conn) +{ + if (http_conn == NULL) + return; + + purple_debug_info("http", "Retrying connection %p...\n", http_conn); + + http_conn->response->code = 0; + _purple_http_disconnect(http_conn, FALSE); + _purple_http_reconnect(http_conn); +} + +void purple_http_conn_cancel_all(PurpleConnection *gc) +{ + GList *gc_list; + + if (purple_debug_is_verbose()) { + purple_debug_misc("http", "Cancelling all running HTTP " + "connections\n"); + } + + gc_list = g_hash_table_lookup(purple_http_hc_by_gc, gc); + + g_hash_table_insert(purple_http_cancelling_gc, gc, GINT_TO_POINTER(TRUE)); + + while (gc_list) { + PurpleHttpConnection *hc = gc_list->data; + gc_list = g_list_next(gc_list); + purple_http_conn_cancel(hc); + } + + g_hash_table_remove(purple_http_cancelling_gc, gc); + + if (NULL != g_hash_table_lookup(purple_http_hc_by_gc, gc)) + purple_debug_fatal("http", "Couldn't cancel all connections " + "related to gc=%p (it shouldn't happen)\n", gc); +} + +gboolean purple_http_conn_is_running(PurpleHttpConnection *http_conn) +{ + if (http_conn == NULL) + return FALSE; + return (NULL != g_hash_table_lookup(purple_http_hc_by_ptr, http_conn)); +} + +PurpleHttpRequest * purple_http_conn_get_request(PurpleHttpConnection *http_conn) +{ + g_return_val_if_fail(http_conn != NULL, NULL); + + return http_conn->request; +} + +PurpleHttpCookieJar * purple_http_conn_get_cookie_jar( + PurpleHttpConnection *http_conn) +{ + return purple_http_request_get_cookie_jar(purple_http_conn_get_request( + http_conn)); +} + +PurpleConnection * purple_http_conn_get_purple_connection( + PurpleHttpConnection *http_conn) +{ + g_return_val_if_fail(http_conn != NULL, NULL); + + return http_conn->gc; +} + +void purple_http_conn_set_progress_watcher(PurpleHttpConnection *http_conn, + PurpleHttpProgressWatcher watcher, gpointer user_data, + gint interval_threshold) +{ + g_return_if_fail(http_conn != NULL); + + if (interval_threshold < 0) { + interval_threshold = + PURPLE_HTTP_PROGRESS_WATCHER_DEFAULT_INTERVAL; + } + + http_conn->watcher = watcher; + http_conn->watcher_user_data = user_data; + http_conn->watcher_interval_threshold = interval_threshold; +} + +static void purple_http_conn_notify_progress_watcher( + PurpleHttpConnection *hc) +{ + gint64 now; + gboolean reading_state; + int processed, total; + + g_return_if_fail(hc != NULL); + + if (hc->watcher == NULL) + return; + + reading_state = hc->is_reading; + if (reading_state) { + total = hc->length_expected; + processed = hc->length_got; + } else { + total = hc->request->contents_length; + processed = hc->request_contents_written; + if (total == 0) + total = -1; + } + if (total != -1 && total < processed) { + purple_debug_warning("http", "Processed too much\n"); + total = processed; + } + + now = g_get_monotonic_time(); + if (hc->watcher_last_call + hc->watcher_interval_threshold + > now && processed != total) + { + if (hc->watcher_delayed_handle) + return; + hc->watcher_delayed_handle = g_timeout_add_seconds( + 1 + hc->watcher_interval_threshold / 1000000, + purple_http_conn_notify_progress_watcher_timeout, hc); + return; + } + + if (hc->watcher_delayed_handle) + g_source_remove(hc->watcher_delayed_handle); + hc->watcher_delayed_handle = 0; + + hc->watcher_last_call = now; + hc->watcher(hc, reading_state, processed, total, hc->watcher_user_data); +} + +static gboolean purple_http_conn_notify_progress_watcher_timeout(gpointer _hc) +{ + PurpleHttpConnection *hc = _hc; + + purple_http_conn_notify_progress_watcher(hc); + + return FALSE; +} + +/*** Cookie jar API ***********************************************************/ + +static PurpleHttpCookie * purple_http_cookie_new(const gchar *value); +void purple_http_cookie_free(PurpleHttpCookie *cookie); + +static void purple_http_cookie_jar_set_ext(PurpleHttpCookieJar *cookie_jar, + const gchar *name, const gchar *value, time_t expires); + +static PurpleHttpCookie * purple_http_cookie_new(const gchar *value) +{ + PurpleHttpCookie *cookie = g_new0(PurpleHttpCookie, 1); + + cookie->value = g_strdup(value); + cookie->expires = -1; + + return cookie; +} + +void purple_http_cookie_free(PurpleHttpCookie *cookie) +{ + g_free(cookie->value); + g_free(cookie); +} + +void purple_http_cookie_jar_free(PurpleHttpCookieJar *cookie_jar); + +PurpleHttpCookieJar * purple_http_cookie_jar_new(void) +{ + PurpleHttpCookieJar *cjar = g_new0(PurpleHttpCookieJar, 1); + + cjar->ref_count = 1; + cjar->tab = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)purple_http_cookie_free); + + return cjar; +} + +void purple_http_cookie_jar_free(PurpleHttpCookieJar *cookie_jar) +{ + g_hash_table_destroy(cookie_jar->tab); + g_free(cookie_jar); +} + +void purple_http_cookie_jar_ref(PurpleHttpCookieJar *cookie_jar) +{ + g_return_if_fail(cookie_jar != NULL); + + cookie_jar->ref_count++; +} + +PurpleHttpCookieJar * purple_http_cookie_jar_unref( + PurpleHttpCookieJar *cookie_jar) +{ + if (cookie_jar == NULL) + return NULL; + + g_return_val_if_fail(cookie_jar->ref_count > 0, NULL); + + cookie_jar->ref_count--; + if (cookie_jar->ref_count > 0) + return cookie_jar; + + purple_http_cookie_jar_free(cookie_jar); + return NULL; +} + +static void purple_http_cookie_jar_parse(PurpleHttpCookieJar *cookie_jar, + GList *values) +{ + values = g_list_first(values); + while (values) { + const gchar *cookie = values->data; + const gchar *eqsign, *semicolon; + gchar *name, *value; + time_t expires = -1; + values = g_list_next(values); + + eqsign = strchr(cookie, '='); + semicolon = strchr(cookie, ';'); + + if (eqsign == NULL || eqsign == cookie || + (semicolon != NULL && semicolon < eqsign)) + { + if (purple_debug_is_unsafe()) + purple_debug_warning("http", + "Invalid cookie: [%s]\n", cookie); + else + purple_debug_warning("http", "Invalid cookie."); + continue; + } + + name = g_strndup(cookie, eqsign - cookie); + eqsign++; + if (semicolon != NULL) + value = g_strndup(eqsign, semicolon - eqsign); + else + value = g_strdup(eqsign); + + if (semicolon != NULL) { + GMatchInfo *match_info; + GRegex *re_expires = g_regex_new( /* XXX: make it static */ + "expires=([a-z0-9, :]+)", + G_REGEX_OPTIMIZE | G_REGEX_CASELESS, + G_REGEX_MATCH_NOTEMPTY, NULL); + + g_regex_match(re_expires, semicolon, 0, &match_info); + if (g_match_info_matches(match_info)) { + gchar *expire_date = + g_match_info_fetch(match_info, 1); + expires = purple_http_rfc1123_to_time( + expire_date); + g_free(expire_date); + } + g_match_info_free(match_info); + + g_regex_unref(re_expires); + } + + purple_http_cookie_jar_set_ext(cookie_jar, name, value, expires); + + g_free(name); + g_free(value); + } +} + +static gchar * purple_http_cookie_jar_gen(PurpleHttpCookieJar *cookie_jar) +{ + GHashTableIter it; + gchar *key; + PurpleHttpCookie *cookie; + GString *str; + time_t now = time(NULL); + + g_return_val_if_fail(cookie_jar != NULL, NULL); + + str = g_string_new(""); + + g_hash_table_iter_init(&it, cookie_jar->tab); + while (g_hash_table_iter_next(&it, (gpointer*)&key, + (gpointer*)&cookie)) + { + if (cookie->expires != -1 && cookie->expires != 0 && cookie->expires <= now) + continue; + g_string_append_printf(str, "%s=%s; ", key, cookie->value); + } + + if (str->len > 0) + g_string_truncate(str, str->len - 2); + return g_string_free(str, FALSE); +} + +void purple_http_cookie_jar_set(PurpleHttpCookieJar *cookie_jar, + const gchar *name, const gchar *value) +{ + gchar *escaped_name = g_strdup(purple_url_encode(name)); + gchar *escaped_value = NULL; + + if (value) { + escaped_value = g_strdup(purple_url_encode(value)); + } + + purple_http_cookie_jar_set_ext(cookie_jar, escaped_name, escaped_value, -1); + + g_free(escaped_name); + g_free(escaped_value); +} + +static void purple_http_cookie_jar_set_ext(PurpleHttpCookieJar *cookie_jar, + const gchar *name, const gchar *value, time_t expires) +{ + g_return_if_fail(cookie_jar != NULL); + g_return_if_fail(name != NULL); + + if (expires != -1 && expires != 0 && time(NULL) >= expires) + value = NULL; + + if (value != NULL) { + PurpleHttpCookie *cookie = purple_http_cookie_new(value); + cookie->expires = expires; + g_hash_table_insert(cookie_jar->tab, g_strdup(name), cookie); + } else + g_hash_table_remove(cookie_jar->tab, name); +} + +gchar * purple_http_cookie_jar_get(PurpleHttpCookieJar *cookie_jar, + const gchar *name) +{ + PurpleHttpCookie *cookie; + + g_return_val_if_fail(cookie_jar != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + cookie = g_hash_table_lookup(cookie_jar->tab, name); + if (!cookie) + return NULL; + + return g_strdup(purple_url_decode(cookie->value)); +} + +gchar * purple_http_cookie_jar_dump(PurpleHttpCookieJar *cjar) +{ + GHashTableIter it; + gchar *key; + PurpleHttpCookie *cookie; + GString *str = g_string_new(""); + + g_hash_table_iter_init(&it, cjar->tab); + while (g_hash_table_iter_next(&it, (gpointer*)&key, (gpointer*)&cookie)) + g_string_append_printf(str, "%s: %s (expires: %" G_GINT64_FORMAT + ")\n", key, cookie->value, (gint64)cookie->expires); + + if (str->len > 0) + g_string_truncate(str, str->len - 1); + return g_string_free(str, FALSE); +} + +gboolean purple_http_cookie_jar_is_empty(PurpleHttpCookieJar *cookie_jar) +{ + g_return_val_if_fail(cookie_jar != NULL, TRUE); + + return g_hash_table_size(cookie_jar->tab) == 0; +} + +/*** HTTP Keep-Alive pool API *************************************************/ + +static void +purple_http_keepalive_host_process_queue(PurpleHttpKeepaliveHost *host); + +static void +purple_http_keepalive_host_free(gpointer _host) +{ + PurpleHttpKeepaliveHost *host = _host; + + g_free(host->host); + + g_slist_free_full(host->queue, + (GDestroyNotify)purple_http_keepalive_pool_request_cancel); + g_slist_free_full(host->sockets, + (GDestroyNotify)purple_http_socket_close_free); + + if (host->process_queue_timeout > 0) { + g_source_remove(host->process_queue_timeout); + host->process_queue_timeout = 0; + } + + + g_free(host); +} + +PurpleHttpKeepalivePool * +purple_http_keepalive_pool_new(void) +{ + PurpleHttpKeepalivePool *pool = g_new0(PurpleHttpKeepalivePool, 1); + + pool->ref_count = 1; + pool->by_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + purple_http_keepalive_host_free); + + return pool; +} + +static void +purple_http_keepalive_pool_free(PurpleHttpKeepalivePool *pool) +{ + g_return_if_fail(pool != NULL); + + if (pool->is_destroying) + return; + pool->is_destroying = TRUE; + g_hash_table_destroy(pool->by_hash); + g_free(pool); +} + +void +purple_http_keepalive_pool_ref(PurpleHttpKeepalivePool *pool) +{ + g_return_if_fail(pool != NULL); + + pool->ref_count++; +} + +PurpleHttpKeepalivePool * +purple_http_keepalive_pool_unref(PurpleHttpKeepalivePool *pool) +{ + if (pool == NULL) + return NULL; + + g_return_val_if_fail(pool->ref_count > 0, NULL); + + pool->ref_count--; + if (pool->ref_count > 0) + return pool; + + purple_http_keepalive_pool_free(pool); + return NULL; +} + +static PurpleHttpKeepaliveRequest * +purple_http_keepalive_pool_request(PurpleHttpKeepalivePool *pool, + PurpleConnection *gc, const gchar *host, int port, gboolean is_ssl, + PurpleSocketConnectCb cb, gpointer user_data) +{ + PurpleHttpKeepaliveRequest *req; + PurpleHttpKeepaliveHost *kahost; + gchar *hash; + + g_return_val_if_fail(pool != NULL, NULL); + g_return_val_if_fail(host != NULL, NULL); + + if (pool->is_destroying) { + purple_debug_error("http", "pool is destroying\n"); + return NULL; + } + + hash = purple_http_socket_hash(host, port, is_ssl); + kahost = g_hash_table_lookup(pool->by_hash, hash); + + if (kahost == NULL) { + kahost = g_new0(PurpleHttpKeepaliveHost, 1); + kahost->pool = pool; + kahost->host = g_strdup(host); + kahost->port = port; + kahost->is_ssl = is_ssl; + + g_hash_table_insert(pool->by_hash, g_strdup(hash), kahost); + } + + g_free(hash); + + req = g_new0(PurpleHttpKeepaliveRequest, 1); + req->gc = gc; + req->cb = cb; + req->user_data = user_data; + req->host = kahost; + + kahost->queue = g_slist_append(kahost->queue, req); + + purple_http_keepalive_host_process_queue(kahost); + + return req; +} + +static void +_purple_http_keepalive_socket_connected(PurpleSocket *ps, + const gchar *error, gpointer _req) +{ + PurpleHttpSocket *hs = NULL; + PurpleHttpKeepaliveRequest *req = _req; + + if (ps != NULL) + hs = purple_socket_get_data(ps, "hs"); + + if (hs != NULL) + hs->use_count++; + + req->cb(ps, error, req->user_data); + g_free(req); +} + +static gboolean +_purple_http_keepalive_host_process_queue_cb(gpointer _host) +{ + PurpleHttpKeepaliveRequest *req; + PurpleHttpKeepaliveHost *host = _host; + PurpleHttpSocket *hs = NULL; + GSList *it; + guint sockets_count; + + g_return_val_if_fail(host != NULL, FALSE); + + host->process_queue_timeout = 0; + + if (host->queue == NULL) + return FALSE; + + sockets_count = 0; + it = host->sockets; + while (it != NULL) { + PurpleHttpSocket *hs_current = it->data; + + sockets_count++; + + if (!hs_current->is_busy) { + hs = hs_current; + break; + } + + it = g_slist_next(it); + } + + /* There are no free sockets and we cannot create another one. */ + if (hs == NULL && sockets_count >= host->pool->limit_per_host && + host->pool->limit_per_host > 0) + { + return FALSE; + } + + req = host->queue->data; + host->queue = g_slist_remove(host->queue, req); + + if (hs != NULL) { + if (purple_debug_is_verbose()) { + purple_debug_misc("http", "locking a (previously used) " + "socket: %p\n", hs); + } + + hs->is_busy = TRUE; + hs->use_count++; + + purple_http_keepalive_host_process_queue(host); + + req->cb(hs->ps, NULL, req->user_data); + g_free(req); + + return FALSE; + } + + hs = purple_http_socket_connect_new(req->gc, req->host->host, + req->host->port, req->host->is_ssl, + _purple_http_keepalive_socket_connected, req); + if (hs == NULL) { + purple_debug_error("http", "failed creating new socket"); + return FALSE; + } + + req->hs = hs; + hs->is_busy = TRUE; + hs->host = host; + + if (purple_debug_is_verbose()) + purple_debug_misc("http", "locking a (new) socket: %p\n", hs); + + host->sockets = g_slist_append(host->sockets, hs); + + return FALSE; +} + +static void +purple_http_keepalive_host_process_queue(PurpleHttpKeepaliveHost *host) +{ + g_return_if_fail(host != NULL); + + if (host->process_queue_timeout > 0) + return; + + host->process_queue_timeout = g_timeout_add(0, + _purple_http_keepalive_host_process_queue_cb, host); +} + +static void +purple_http_keepalive_pool_request_cancel(PurpleHttpKeepaliveRequest *req) +{ + if (req == NULL) + return; + + if (req->host != NULL) + req->host->queue = g_slist_remove(req->host->queue, req); + + if (req->hs != NULL) { + if (G_LIKELY(req->host)) { + req->host->sockets = g_slist_remove(req->host->sockets, + req->hs); + } + purple_http_socket_close_free(req->hs); + /* req should already be free'd here */ + } else { + req->cb(NULL, _("Cancelled"), req->user_data); + g_free(req); + } +} + +static void +purple_http_keepalive_pool_release(PurpleHttpSocket *hs, gboolean invalidate) +{ + PurpleHttpKeepaliveHost *host; + + if (hs == NULL) + return; + + if (purple_debug_is_verbose()) + purple_debug_misc("http", "releasing a socket: %p\n", hs); + + purple_socket_watch(hs->ps, 0, NULL, NULL); + hs->is_busy = FALSE; + host = hs->host; + + if (host == NULL) { + purple_http_socket_close_free(hs); + return; + } + + if (invalidate) { + host->sockets = g_slist_remove(host->sockets, hs); + purple_http_socket_close_free(hs); + } + + purple_http_keepalive_host_process_queue(host); +} + +void +purple_http_keepalive_pool_set_limit_per_host(PurpleHttpKeepalivePool *pool, + guint limit) +{ + g_return_if_fail(pool != NULL); + + pool->limit_per_host = limit; +} + +guint +purple_http_keepalive_pool_get_limit_per_host(PurpleHttpKeepalivePool *pool) +{ + g_return_val_if_fail(pool != NULL, 0); + + return pool->limit_per_host; +} + +/*** HTTP connection set API **************************************************/ + +PurpleHttpConnectionSet * +purple_http_connection_set_new(void) +{ + PurpleHttpConnectionSet *set; + + set = g_new0(PurpleHttpConnectionSet, 1); + set->connections = g_hash_table_new(g_direct_hash, g_direct_equal); + + return set; +} + +void +purple_http_connection_set_destroy(PurpleHttpConnectionSet *set) +{ + if (set == NULL) + return; + + set->is_destroying = TRUE; + + while (TRUE) { + GHashTableIter iter; + PurpleHttpConnection *http_conn; + + g_hash_table_iter_init(&iter, set->connections); + if (!g_hash_table_iter_next(&iter, (gpointer*)&http_conn, NULL)) + break; + + purple_http_conn_cancel(http_conn); + } + + g_hash_table_destroy(set->connections); + g_free(set); +} + +void +purple_http_connection_set_add(PurpleHttpConnectionSet *set, + PurpleHttpConnection *http_conn) +{ + if (set->is_destroying) + return; + if (http_conn->connection_set == set) + return; + if (http_conn->connection_set != NULL) { + purple_http_connection_set_remove(http_conn->connection_set, + http_conn); + } + g_hash_table_insert(set->connections, http_conn, (gpointer)TRUE); + http_conn->connection_set = set; +} + +static void +purple_http_connection_set_remove(PurpleHttpConnectionSet *set, + PurpleHttpConnection *http_conn) +{ + g_hash_table_remove(set->connections, http_conn); + if (http_conn->connection_set == set) + http_conn->connection_set = NULL; +} + +/*** Request API **************************************************************/ + +static void purple_http_request_free(PurpleHttpRequest *request); + +PurpleHttpRequest * purple_http_request_new(const gchar *url) +{ + PurpleHttpRequest *request; + + request = g_new0(PurpleHttpRequest, 1); + + request->ref_count = 1; + request->url = g_strdup(url); + request->headers = purple_http_headers_new(); + request->cookie_jar = purple_http_cookie_jar_new(); + request->keepalive_pool = purple_http_keepalive_pool_new(); + + request->timeout = PURPLE_HTTP_REQUEST_DEFAULT_TIMEOUT; + request->max_redirects = PURPLE_HTTP_REQUEST_DEFAULT_MAX_REDIRECTS; + request->http11 = TRUE; + request->max_length = PURPLE_HTTP_REQUEST_DEFAULT_MAX_LENGTH; + + return request; +} + +static void purple_http_request_free(PurpleHttpRequest *request) +{ + purple_http_headers_free(request->headers); + purple_http_cookie_jar_unref(request->cookie_jar); + purple_http_keepalive_pool_unref(request->keepalive_pool); + g_free(request->method); + g_free(request->contents); + g_free(request->url); + g_free(request); +} + +void purple_http_request_ref(PurpleHttpRequest *request) +{ + g_return_if_fail(request != NULL); + + request->ref_count++; +} + +PurpleHttpRequest * purple_http_request_unref(PurpleHttpRequest *request) +{ + if (request == NULL) + return NULL; + + g_return_val_if_fail(request->ref_count > 0, NULL); + + request->ref_count--; + if (request->ref_count > 0) + return request; + + purple_http_request_free(request); + return NULL; +} + +void purple_http_request_set_url(PurpleHttpRequest *request, const gchar *url) +{ + g_return_if_fail(request != NULL); + g_return_if_fail(url != NULL); + + g_free(request->url); + request->url = g_strdup(url); +} + +void purple_http_request_set_url_printf(PurpleHttpRequest *request, + const gchar *format, ...) +{ + va_list args; + gchar *value; + + g_return_if_fail(request != NULL); + g_return_if_fail(format != NULL); + + va_start(args, format); + value = g_strdup_vprintf(format, args); + va_end(args); + + purple_http_request_set_url(request, value); + g_free(value); +} + +const gchar * purple_http_request_get_url(PurpleHttpRequest *request) +{ + g_return_val_if_fail(request != NULL, NULL); + + return request->url; +} + +void purple_http_request_set_method(PurpleHttpRequest *request, const gchar *method) +{ + g_return_if_fail(request != NULL); + + g_free(request->method); + request->method = g_strdup(method); +} + +const gchar * purple_http_request_get_method(PurpleHttpRequest *request) +{ + g_return_val_if_fail(request != NULL, NULL); + + return request->method; +} + +static gboolean purple_http_request_is_method(PurpleHttpRequest *request, + const gchar *method) +{ + const gchar *rmethod; + + g_return_val_if_fail(request != NULL, FALSE); + g_return_val_if_fail(method != NULL, FALSE); + + rmethod = purple_http_request_get_method(request); + if (rmethod == NULL) + return (g_ascii_strcasecmp(method, "get") == 0); + return (g_ascii_strcasecmp(method, rmethod) == 0); +} + +void +purple_http_request_set_keepalive_pool(PurpleHttpRequest *request, + PurpleHttpKeepalivePool *pool) +{ + g_return_if_fail(request != NULL); + + if (pool != NULL) + purple_http_keepalive_pool_ref(pool); + + if (request->keepalive_pool != NULL) { + purple_http_keepalive_pool_unref(request->keepalive_pool); + request->keepalive_pool = NULL; + } + + if (pool != NULL) + request->keepalive_pool = pool; +} + +PurpleHttpKeepalivePool * +purple_http_request_get_keepalive_pool(PurpleHttpRequest *request) +{ + g_return_val_if_fail(request != NULL, FALSE); + + return request->keepalive_pool; +} + +void purple_http_request_set_contents(PurpleHttpRequest *request, + const gchar *contents, int length) +{ + g_return_if_fail(request != NULL); + g_return_if_fail(length >= -1); + + request->contents_reader = NULL; + request->contents_reader_data = NULL; + + g_free(request->contents); + if (contents == NULL || length == 0) { + request->contents = NULL; + request->contents_length = 0; + return; + } + + if (length == -1) + length = strlen(contents); + request->contents = g_memdup2(contents, length); + request->contents_length = length; +} + +void purple_http_request_set_contents_reader(PurpleHttpRequest *request, + PurpleHttpContentReader reader, int contents_length, gpointer user_data) +{ + g_return_if_fail(request != NULL); + g_return_if_fail(reader != NULL); + g_return_if_fail(contents_length >= -1); + + g_free(request->contents); + request->contents = NULL; + request->contents_length = contents_length; + request->contents_reader = reader; + request->contents_reader_data = user_data; +} + +void purple_http_request_set_response_writer(PurpleHttpRequest *request, + PurpleHttpContentWriter writer, gpointer user_data) +{ + g_return_if_fail(request != NULL); + + if (writer == NULL) + user_data = NULL; + request->response_writer = writer; + request->response_writer_data = user_data; +} + +void purple_http_request_set_timeout(PurpleHttpRequest *request, int timeout) +{ + g_return_if_fail(request != NULL); + + if (timeout < -1) + timeout = -1; + + request->timeout = timeout; +} + +int purple_http_request_get_timeout(PurpleHttpRequest *request) +{ + g_return_val_if_fail(request != NULL, -1); + + return request->timeout; +} + +void purple_http_request_set_max_redirects(PurpleHttpRequest *request, + int max_redirects) +{ + g_return_if_fail(request != NULL); + + if (max_redirects < -1) + max_redirects = -1; + + request->max_redirects = max_redirects; +} + +int purple_http_request_get_max_redirects(PurpleHttpRequest *request) +{ + g_return_val_if_fail(request != NULL, -1); + + return request->max_redirects; +} + +void purple_http_request_set_cookie_jar(PurpleHttpRequest *request, + PurpleHttpCookieJar *cookie_jar) +{ + g_return_if_fail(request != NULL); + g_return_if_fail(cookie_jar != NULL); + + purple_http_cookie_jar_ref(cookie_jar); + purple_http_cookie_jar_unref(request->cookie_jar); + request->cookie_jar = cookie_jar; +} + +PurpleHttpCookieJar * purple_http_request_get_cookie_jar( + PurpleHttpRequest *request) +{ + g_return_val_if_fail(request != NULL, NULL); + + return request->cookie_jar; +} + +void purple_http_request_set_http11(PurpleHttpRequest *request, gboolean http11) +{ + g_return_if_fail(request != NULL); + + request->http11 = http11; +} + +gboolean purple_http_request_is_http11(PurpleHttpRequest *request) +{ + g_return_val_if_fail(request != NULL, FALSE); + + return request->http11; +} + +void purple_http_request_set_max_len(PurpleHttpRequest *request, int max_len) +{ + g_return_if_fail(request != NULL); + + if (max_len < 0 || max_len > PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH) + max_len = PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH; + + request->max_length = max_len; +} + +int purple_http_request_get_max_len(PurpleHttpRequest *request) +{ + g_return_val_if_fail(request != NULL, -1); + + return request->max_length; +} + +void purple_http_request_header_set(PurpleHttpRequest *request, + const gchar *key, const gchar *value) +{ + g_return_if_fail(request != NULL); + g_return_if_fail(key != NULL); + + purple_http_headers_remove(request->headers, key); + if (value) + purple_http_headers_add(request->headers, key, value); +} + +void purple_http_request_header_set_printf(PurpleHttpRequest *request, + const gchar *key, const gchar *format, ...) +{ + va_list args; + gchar *value; + + g_return_if_fail(request != NULL); + g_return_if_fail(key != NULL); + g_return_if_fail(format != NULL); + + va_start(args, format); + value = g_strdup_vprintf(format, args); + va_end(args); + + purple_http_request_header_set(request, key, value); + g_free(value); +} + +void purple_http_request_header_add(PurpleHttpRequest *request, + const gchar *key, const gchar *value) +{ + g_return_if_fail(request != NULL); + g_return_if_fail(key != NULL); + + purple_http_headers_add(request->headers, key, value); +} + +/*** HTTP response API ********************************************************/ + +static PurpleHttpResponse * purple_http_response_new(void) +{ + PurpleHttpResponse *response = g_new0(PurpleHttpResponse, 1); + + return response; +} + +static void purple_http_response_free(PurpleHttpResponse *response) +{ + if (response->contents != NULL) + g_string_free(response->contents, TRUE); + g_free(response->error); + purple_http_headers_free(response->headers); + g_free(response); +} + +gboolean purple_http_response_is_successful(PurpleHttpResponse *response) +{ + int code; + + g_return_val_if_fail(response != NULL, FALSE); + + code = response->code; + + if (code <= 0) + return FALSE; + + /* TODO: HTTP/1.1 100 Continue */ + + if (code / 100 == 2) + return TRUE; + + return FALSE; +} + +int purple_http_response_get_code(PurpleHttpResponse *response) +{ + g_return_val_if_fail(response != NULL, 0); + + return response->code; +} + +const gchar * purple_http_response_get_error(PurpleHttpResponse *response) +{ + g_return_val_if_fail(response != NULL, NULL); + + if (response->error != NULL) + return response->error; + + if (!purple_http_response_is_successful(response)) { + static gchar errmsg[200]; + if (response->code <= 0) { + g_snprintf(errmsg, sizeof(errmsg), + _("Unknown HTTP error")); + } else { + g_snprintf(errmsg, sizeof(errmsg), + _("Invalid HTTP response code (%d)"), + response->code); + } + return errmsg; + } + + return NULL; +} + +gsize purple_http_response_get_data_len(PurpleHttpResponse *response) +{ + g_return_val_if_fail(response != NULL, 0); + + if (response->contents == NULL) + return 0; + + return response->contents->len; +} + +const gchar * purple_http_response_get_data(PurpleHttpResponse *response, size_t *len) +{ + const gchar *ret = ""; + + g_return_val_if_fail(response != NULL, ""); + + if (response->contents != NULL) { + ret = response->contents->str; + if (len) + *len = response->contents->len; + } else { + if (len) + *len = 0; + } + + return ret; +} + +const GList * purple_http_response_get_all_headers(PurpleHttpResponse *response) +{ + g_return_val_if_fail(response != NULL, NULL); + + return purple_http_headers_get_all(response->headers); +} + +const GList * purple_http_response_get_headers_by_name( + PurpleHttpResponse *response, const gchar *name) +{ + g_return_val_if_fail(response != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + return purple_http_headers_get_all_by_name(response->headers, name); +} + +const gchar * purple_http_response_get_header(PurpleHttpResponse *response, + const gchar *name) +{ + g_return_val_if_fail(response != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + return purple_http_headers_get(response->headers, name); +} + +/*** URL functions ************************************************************/ + +PurpleHttpURL * +purple_http_url_parse(const char *raw_url) +{ + PurpleHttpURL *url; + GMatchInfo *match_info; + + gchar *host_full, *tmp; + + g_return_val_if_fail(raw_url != NULL, NULL); + + if (!g_regex_match(purple_http_re_url, raw_url, 0, &match_info)) { + if (purple_debug_is_verbose() && purple_debug_is_unsafe()) { + purple_debug_warning("http", + "Invalid URL provided: %s\n", + raw_url); + } + return NULL; + } + + url = g_new0(PurpleHttpURL, 1); + + url->protocol = g_match_info_fetch(match_info, 1); + host_full = g_match_info_fetch(match_info, 2); + url->path = g_match_info_fetch(match_info, 3); + url->fragment = g_match_info_fetch(match_info, 4); + g_match_info_free(match_info); + + if (g_strcmp0(url->protocol, "") == 0) { + g_free(url->protocol); + url->protocol = NULL; + } else if (url->protocol != NULL) { + tmp = url->protocol; + url->protocol = g_ascii_strdown(url->protocol, -1); + g_free(tmp); + } + if (host_full[0] == '\0') { + g_free(host_full); + host_full = NULL; + } + if (url->path[0] == '\0') { + g_free(url->path); + url->path = NULL; + } + if ((url->protocol == NULL) != (host_full == NULL)) + purple_debug_warning("http", "Protocol or host not present " + "(unlikely case)\n"); + + if (host_full) { + gchar *port_str; + + if (!g_regex_match(purple_http_re_url_host, host_full, 0, + &match_info)) + { + if (purple_debug_is_verbose() && + purple_debug_is_unsafe()) + { + purple_debug_warning("http", + "Invalid host provided for URL: %s\n", + raw_url); + } + + g_free(host_full); + purple_http_url_free(url); + return NULL; + } + + url->username = g_match_info_fetch(match_info, 1); + url->password = g_match_info_fetch(match_info, 2); + url->host = g_match_info_fetch(match_info, 3); + port_str = g_match_info_fetch(match_info, 4); + + if (port_str && port_str[0]) + url->port = atoi(port_str); + + if (url->username[0] == '\0') { + g_free(url->username); + url->username = NULL; + } + if (url->password[0] == '\0') { + g_free(url->password); + url->password = NULL; + } + if (g_strcmp0(url->host, "") == 0) { + g_free(url->host); + url->host = NULL; + } else if (url->host != NULL) { + tmp = url->host; + url->host = g_ascii_strdown(url->host, -1); + g_free(tmp); + } + + g_free(port_str); + g_match_info_free(match_info); + + g_free(host_full); + host_full = NULL; + } + + if (url->host != NULL) { + if (url->protocol == NULL) + url->protocol = g_strdup("http"); + if (url->port == 0 && 0 == strcmp(url->protocol, "http")) + url->port = 80; + if (url->port == 0 && 0 == strcmp(url->protocol, "https")) + url->port = 443; + if (url->path == NULL) + url->path = g_strdup("/"); + if (url->path[0] != '/') + purple_debug_warning("http", + "URL path doesn't start with slash\n"); + } + + return url; +} + +void +purple_http_url_free(PurpleHttpURL *parsed_url) +{ + if (parsed_url == NULL) + return; + + g_free(parsed_url->protocol); + g_free(parsed_url->username); + g_free(parsed_url->password); + g_free(parsed_url->host); + g_free(parsed_url->path); + g_free(parsed_url->fragment); + g_free(parsed_url); +} + +void +purple_http_url_relative(PurpleHttpURL *base_url, PurpleHttpURL *relative_url) +{ + g_return_if_fail(base_url != NULL); + g_return_if_fail(relative_url != NULL); + + if (relative_url->host) { + g_free(base_url->protocol); + base_url->protocol = g_strdup(relative_url->protocol); + g_free(base_url->username); + base_url->username = g_strdup(relative_url->username); + g_free(base_url->password); + base_url->password = g_strdup(relative_url->password); + g_free(base_url->host); + base_url->host = g_strdup(relative_url->host); + base_url->port = relative_url->port; + + g_free(base_url->path); + base_url->path = NULL; + } + + if (relative_url->path) { + if (relative_url->path[0] == '/' || + base_url->path == NULL) + { + g_free(base_url->path); + base_url->path = g_strdup(relative_url->path); + } else { + gchar *last_slash = strrchr(base_url->path, '/'); + gchar *tmp; + if (last_slash == NULL) + base_url->path[0] = '\0'; + else + last_slash[1] = '\0'; + tmp = base_url->path; + base_url->path = g_strconcat(base_url->path, + relative_url->path, NULL); + g_free(tmp); + } + } + + g_free(base_url->fragment); + base_url->fragment = g_strdup(relative_url->fragment); +} + +gchar * +purple_http_url_print(PurpleHttpURL *parsed_url) +{ + GString *url = g_string_new(""); + gboolean before_host_printed = FALSE, host_printed = FALSE; + gboolean port_is_default = FALSE; + + if (parsed_url->protocol) { + g_string_append_printf(url, "%s://", parsed_url->protocol); + before_host_printed = TRUE; + if (parsed_url->port == 80 && 0 == strcmp(parsed_url->protocol, + "http")) + port_is_default = TRUE; + if (parsed_url->port == 443 && 0 == strcmp(parsed_url->protocol, + "https")) + port_is_default = TRUE; + } + if (parsed_url->username || parsed_url->password) { + if (parsed_url->username) + g_string_append(url, parsed_url->username); + g_string_append_printf(url, ":%s", parsed_url->password); + g_string_append(url, "@"); + before_host_printed = TRUE; + } + if (parsed_url->host || parsed_url->port) { + if (!parsed_url->host) + g_string_append_printf(url, "{???}:%d", + parsed_url->port); + else { + g_string_append(url, parsed_url->host); + if (!port_is_default) + g_string_append_printf(url, ":%d", + parsed_url->port); + } + host_printed = TRUE; + } + if (parsed_url->path) { + if (!host_printed && before_host_printed) + g_string_append(url, "{???}"); + g_string_append(url, parsed_url->path); + } + if (parsed_url->fragment) + g_string_append_printf(url, "#%s", parsed_url->fragment); + + return g_string_free(url, FALSE); +} + +const gchar * +purple_http_url_get_protocol(const PurpleHttpURL *parsed_url) +{ + g_return_val_if_fail(parsed_url != NULL, NULL); + + return parsed_url->protocol; +} + +const gchar * +purple_http_url_get_username(const PurpleHttpURL *parsed_url) +{ + g_return_val_if_fail(parsed_url != NULL, NULL); + + return parsed_url->username; +} + +const gchar * +purple_http_url_get_password(const PurpleHttpURL *parsed_url) +{ + g_return_val_if_fail(parsed_url != NULL, NULL); + + return parsed_url->password; +} + +const gchar * +purple_http_url_get_host(const PurpleHttpURL *parsed_url) +{ + g_return_val_if_fail(parsed_url != NULL, NULL); + + return parsed_url->host; +} + +int +purple_http_url_get_port(const PurpleHttpURL *parsed_url) +{ + g_return_val_if_fail(parsed_url != NULL, 0); + + return parsed_url->port; +} + +const gchar * +purple_http_url_get_path(const PurpleHttpURL *parsed_url) +{ + g_return_val_if_fail(parsed_url != NULL, NULL); + + return parsed_url->path; +} + +const gchar * +purple_http_url_get_fragment(const PurpleHttpURL *parsed_url) +{ + g_return_val_if_fail(parsed_url != NULL, NULL); + + return parsed_url->fragment; +} + +/*** HTTP Subsystem ***********************************************************/ + +void purple_http_init(void) +{ + purple_http_re_url = g_regex_new("^" + + "(?:" /* host part beginning */ + "([a-z]+)\\:/*" /* protocol */ + "([^/]+)" /* username, password, host, port */ + ")?" /* host part ending */ + + "([^#]*)" /* path */ + + "(?:#" "(.*)" ")?" /* fragment */ + + "$", G_REGEX_OPTIMIZE | G_REGEX_CASELESS, + G_REGEX_MATCH_NOTEMPTY, NULL); + + purple_http_re_url_host = g_regex_new("^" + + "(?:" /* user credentials part beginning */ + "([" PURPLE_HTTP_URL_CREDENTIALS_CHARS "]+)" /* username */ + "(?::([" PURPLE_HTTP_URL_CREDENTIALS_CHARS "]+))" /* password */ + "@)?" /* user credentials part ending */ + + "([a-z0-9.-]+)" /* host */ + "(?::([0-9]+))?" /* port*/ + + "$", G_REGEX_OPTIMIZE | G_REGEX_CASELESS, + G_REGEX_MATCH_NOTEMPTY, NULL); + + purple_http_re_rfc1123 = g_regex_new( + "^[a-z]+, " /* weekday */ + "([0-9]+) " /* date */ + "([a-z]+) " /* month */ + "([0-9]+) " /* year */ + "([0-9]+:[0-9]+:[0-9]+) " /* time */ + "(?:GMT|UTC)$", + G_REGEX_OPTIMIZE | G_REGEX_CASELESS, + G_REGEX_MATCH_NOTEMPTY, NULL); + + purple_http_hc_list = NULL; + purple_http_hc_by_ptr = g_hash_table_new(g_direct_hash, g_direct_equal); + purple_http_hc_by_gc = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, (GDestroyNotify)g_list_free); + purple_http_cancelling_gc = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +static void purple_http_foreach_conn_cancel(gpointer _hc, gpointer user_data) +{ + PurpleHttpConnection *hc = _hc; + purple_http_conn_cancel(hc); +} + +void purple_http_uninit(void) +{ + g_regex_unref(purple_http_re_url); + purple_http_re_url = NULL; + g_regex_unref(purple_http_re_url_host); + purple_http_re_url_host = NULL; + g_regex_unref(purple_http_re_rfc1123); + purple_http_re_rfc1123 = NULL; + + g_list_foreach(purple_http_hc_list, purple_http_foreach_conn_cancel, + NULL); + + if (purple_http_hc_list != NULL || + 0 != g_hash_table_size(purple_http_hc_by_ptr) || + 0 != g_hash_table_size(purple_http_hc_by_gc)) + purple_debug_warning("http", + "Couldn't cleanup all connections.\n"); + + g_list_free(purple_http_hc_list); + purple_http_hc_list = NULL; + g_hash_table_destroy(purple_http_hc_by_gc); + purple_http_hc_by_gc = NULL; + g_hash_table_destroy(purple_http_hc_by_ptr); + purple_http_hc_by_ptr = NULL; + g_hash_table_destroy(purple_http_cancelling_gc); + purple_http_cancelling_gc = NULL; +} diff --git a/pidgin/libpurple/http.h b/pidgin/libpurple/http.h new file mode 100644 index 00000000..870d7664 --- /dev/null +++ b/pidgin/libpurple/http.h @@ -0,0 +1,964 @@ +/* purple + * + * 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 + */ + +#ifndef _PURPLE_HTTP_H_ +#define _PURPLE_HTTP_H_ +/** + * SECTION:http + * @section_id: libpurple-http + * @short_description: http.h + * @title: HTTP API + */ + +#include + +#include "connection.h" + +/** + * PurpleHttpRequest: + * + * A structure containing all data required to generate a single HTTP request. + */ +typedef struct _PurpleHttpRequest PurpleHttpRequest; + +/** + * PurpleHttpConnection: + * + * A representation of actually running HTTP request. Can be used to cancel the + * request. + */ +typedef struct _PurpleHttpConnection PurpleHttpConnection; + +/** + * PurpleHttpResponse: + * + * All information got with response for HTTP request. + */ +typedef struct _PurpleHttpResponse PurpleHttpResponse; + +/** + * PurpleHttpURL: + * + * Parsed representation for the URL. + */ +typedef struct _PurpleHttpURL PurpleHttpURL; + +/** + * PurpleHttpCookieJar: + * + * An collection of cookies, got from HTTP response or provided for HTTP + * request. + */ +typedef struct _PurpleHttpCookieJar PurpleHttpCookieJar; + +/** + * PurpleHttpKeepalivePool: + * + * A pool of TCP connections for HTTP Keep-Alive session. + */ +typedef struct _PurpleHttpKeepalivePool PurpleHttpKeepalivePool; + +/** + * PurpleHttpConnectionSet: + * + * A set of running HTTP requests. Can be used to cancel all of them at once. + */ +typedef struct _PurpleHttpConnectionSet PurpleHttpConnectionSet; + +/** + * PurpleHttpCallback: + * + * An callback called after performing (successfully or not) HTTP request. + */ +typedef void (*PurpleHttpCallback)(PurpleHttpConnection *http_conn, + PurpleHttpResponse *response, gpointer user_data); + +/** + * PurpleHttpContentReaderCb: + * + * An callback called after storing data requested by PurpleHttpContentReader. + */ +typedef void (*PurpleHttpContentReaderCb)(PurpleHttpConnection *http_conn, + gboolean success, gboolean eof, size_t stored); + +/** + * PurpleHttpContentReader: + * @http_conn: Connection, which requests data. + * @buffer: Buffer to store data to (with offset ignored). + * @offset: Position, from where to read data. + * @length: Length of data to read. + * @user_data: The user data passed with callback function. + * @cb: The function to call after storing data to buffer. + * + * An callback for getting large request contents (ie. from file stored on + * disk). + */ +typedef void (*PurpleHttpContentReader)(PurpleHttpConnection *http_conn, + gchar *buffer, size_t offset, size_t length, gpointer user_data, + PurpleHttpContentReaderCb cb); + +/** + * PurpleHttpContentWriter: + * @http_conn: Connection, which requests data. + * @response: Response at point got so far (may change later). + * @buffer: Buffer to read data from (with offset ignored). + * @offset: Position of data got (its value is offset + length of + * previous call), can be safely ignored. + * @length: Length of data read. + * @user_data: The user data passed with callback function. + * + * An callback for writting large response contents. + * + * Returns: TRUE, if succeeded, FALSE otherwise. + */ +typedef gboolean (*PurpleHttpContentWriter)(PurpleHttpConnection *http_conn, + PurpleHttpResponse *response, const gchar *buffer, size_t offset, + size_t length, gpointer user_data); + +/** + * PurpleHttpProgressWatcher: + * @http_conn: The HTTP Connection. + * @reading_state: FALSE, is we are sending the request, TRUE, when reading + * the response. + * @processed: The amount of data already processed. + * @total: Total amount of data (in current state). + * @user_data: The user data passed with callback function. + * + * An callback for watching HTTP connection progress. + */ +typedef void (*PurpleHttpProgressWatcher)(PurpleHttpConnection *http_conn, + gboolean reading_state, int processed, int total, gpointer user_data); + +G_BEGIN_DECLS + +/**************************************************************************/ +/* Performing HTTP requests */ +/**************************************************************************/ + +/** + * purple_http_get: + * @gc: The connection for which the request is needed, or NULL. + * @callback: (scope call): The callback function. + * @user_data: The user data to pass to the callback function. + * @url: The URL. + * + * Fetches the data from a URL with GET request, and passes it to a callback + * function. + * + * Returns: The HTTP connection struct. + */ +PurpleHttpConnection * purple_http_get(PurpleConnection *gc, + PurpleHttpCallback callback, gpointer user_data, const gchar *url); + +/** + * purple_http_get_printf: + * @gc: The connection for which the request is needed, or NULL. + * @callback: (scope call): The callback function. + * @user_data: The user data to pass to the callback function. + * @format: The format string. + * @...: The parameters to insert into the format string. + * + * Constructs an URL and fetches the data from it with GET request, then passes + * it to a callback function. + * + * Returns: The HTTP connection struct. + */ +PurpleHttpConnection * purple_http_get_printf(PurpleConnection *gc, + PurpleHttpCallback callback, gpointer user_data, + const gchar *format, ...) G_GNUC_PRINTF(4, 5); + +/** + * purple_http_request: + * @gc: The connection for which the request is needed, or NULL. + * @request: The request. + * @callback: (scope call): The callback function. + * @user_data: The user data to pass to the callback function. + * + * Fetches a HTTP request and passes the response to a callback function. + * Provided request struct can be shared by multiple http requests but can not + * be modified when any of these is running. + * + * Returns: The HTTP connection struct. + */ +PurpleHttpConnection * purple_http_request(PurpleConnection *gc, + PurpleHttpRequest *request, PurpleHttpCallback callback, + gpointer user_data); + +/**************************************************************************/ +/* HTTP connection API */ +/**************************************************************************/ + +/** + * purple_http_conn_cancel: + * @http_conn: The data returned when you initiated the HTTP request. + * + * Cancel a pending HTTP request. + */ +void purple_http_conn_cancel(PurpleHttpConnection *http_conn); + +/** + * purple_http_conn_cancel_all: + * @gc: The handle. + * + * Cancels all HTTP connections associated with the specified handle. + */ +void purple_http_conn_cancel_all(PurpleConnection *gc); + +/** + * purple_http_conn_is_running: + * @http_conn: The HTTP connection (may be invalid pointer). + * + * Checks, if provided HTTP request is running. + * + * Returns: TRUE, if provided connection is currently running. + */ +gboolean purple_http_conn_is_running(PurpleHttpConnection *http_conn); + +/** + * purple_http_conn_get_request: + * @http_conn: The HTTP connection. + * + * Gets PurpleHttpRequest used for specified HTTP connection. + * + * Returns: The PurpleHttpRequest object. + */ +PurpleHttpRequest * purple_http_conn_get_request( + PurpleHttpConnection *http_conn); + +/** + * purple_http_conn_get_cookie_jar: + * @http_conn: The HTTP connection. + * + * Gets cookie jar used within connection. + * + * Returns: The cookie jar. + */ +PurpleHttpCookieJar * purple_http_conn_get_cookie_jar( + PurpleHttpConnection *http_conn); + +/** + * purple_http_conn_get_purple_connection: + * @http_conn: The HTTP connection. + * + * Gets PurpleConnection tied with specified HTTP connection. + * + * Returns: The PurpleConnection object. + */ +PurpleConnection * purple_http_conn_get_purple_connection( + PurpleHttpConnection *http_conn); + +/** + * purple_http_conn_set_progress_watcher: + * @http_conn: The HTTP connection. + * @watcher: (scope call): The watcher. + * @user_data: The user data to pass to the callback function. + * @interval_threshold: Minimum interval (in microseconds) of calls to + * watcher, or -1 for default. + * + * Sets the watcher, called after writing or reading data to/from HTTP stream. + * May be used for updating transfer progress gauge. + */ +void purple_http_conn_set_progress_watcher(PurpleHttpConnection *http_conn, + PurpleHttpProgressWatcher watcher, gpointer user_data, + gint interval_threshold); + + +/**************************************************************************/ +/* URL processing API */ +/**************************************************************************/ + +/** + * purple_http_url_parse: + * @url: The URL to parse. + * + * Parses a URL. + * + * The returned data must be freed with purple_http_url_free. + * + * Returns: The parsed url or NULL, if the URL is invalid. + */ +PurpleHttpURL * +purple_http_url_parse(const char *url); + +/** + * purple_http_url_free: + * @parsed_url: The parsed URL struct, or NULL. + * + * Frees the parsed URL struct. + */ +void +purple_http_url_free(PurpleHttpURL *parsed_url); + +/** + * purple_http_url_relative: + * @base_url: The base URL. The result is stored here. + * @relative_url: The relative URL. + * + * Converts the base URL to the absolute form of the provided relative URL. + * + * Example: "https://example.com/path/to/file.html" + "subdir/other-file.html" = + * "https://example.com/path/to/subdir/another-file.html" + */ +void +purple_http_url_relative(PurpleHttpURL *base_url, PurpleHttpURL *relative_url); + +/** + * purple_http_url_print: + * @parsed_url: The URL struct. + * + * Converts the URL struct to the printable form. The result may not be a valid + * URL (in cases, when the struct doesn't have all fields filled properly). + * + * The result must be g_free'd. + * + * Returns: The printable form of the URL. + */ +gchar * +purple_http_url_print(PurpleHttpURL *parsed_url); + +/** + * purple_http_url_get_protocol: + * @parsed_url: The URL struct. + * + * Gets the protocol part of URL. + * + * Returns: The protocol. + */ +const gchar * +purple_http_url_get_protocol(const PurpleHttpURL *parsed_url); + +/** + * purple_http_url_get_username: + * @parsed_url: The URL struct. + * + * Gets the username part of URL. + * + * Returns: The username. + */ +const gchar * +purple_http_url_get_username(const PurpleHttpURL *parsed_url); + +/** + * purple_http_url_get_password: + * @parsed_url: The URL struct. + * + * Gets the password part of URL. + * + * Returns: The password. + */ +const gchar * +purple_http_url_get_password(const PurpleHttpURL *parsed_url); + +/** + * purple_http_url_get_host: + * @parsed_url: The URL struct. + * + * Gets the hostname part of URL. + * + * Returns: The hostname. + */ +const gchar * +purple_http_url_get_host(const PurpleHttpURL *parsed_url); + +/** + * purple_http_url_get_port: + * @parsed_url: The URL struct. + * + * Gets the port part of URL. + * + * Returns: The port number. + */ +int +purple_http_url_get_port(const PurpleHttpURL *parsed_url); + +/** + * purple_http_url_get_path: + * @parsed_url: The URL struct. + * + * Gets the path part of URL. + * + * Returns: The path. + */ +const gchar * +purple_http_url_get_path(const PurpleHttpURL *parsed_url); + +/** + * purple_http_url_get_fragment: + * @parsed_url: The URL struct. + * + * Gets the fragment part of URL. + * + * Returns: The fragment. + */ +const gchar * +purple_http_url_get_fragment(const PurpleHttpURL *parsed_url); + + +/**************************************************************************/ +/* Cookie jar API */ +/**************************************************************************/ + +/** + * purple_http_cookie_jar_new: + * + * Creates new cookie jar, + * + * Returns: empty cookie jar. + */ +PurpleHttpCookieJar * purple_http_cookie_jar_new(void); + +/** + * purple_http_cookie_jar_ref: + * @cookie_jar: The cookie jar. + * + * Increment the reference count. + */ +void purple_http_cookie_jar_ref(PurpleHttpCookieJar *cookie_jar); + +/** + * purple_http_cookie_jar_unref: + * @cookie_jar: The cookie jar. + * + * Decrement the reference count. + * + * If the reference count reaches zero, the cookie jar will be freed. + * + * Returns: @cookie_jar or %NULL if the reference count reached zero. + */ +PurpleHttpCookieJar * purple_http_cookie_jar_unref( + PurpleHttpCookieJar *cookie_jar); + +/** + * purple_http_cookie_jar_set: + * @cookie_jar: The cookie jar. + * @name: Cookie name. + * @value: Cookie contents. + * + * Sets the cookie. + */ +void purple_http_cookie_jar_set(PurpleHttpCookieJar *cookie_jar, + const gchar *name, const gchar *value); + +/** + * purple_http_cookie_jar_get: + * @cookie_jar: The cookie jar. + * @name: Cookie name. + * + * Gets the cookie. + * + * The result must be g_free'd. + * + * Returns: Cookie contents, or NULL, if cookie doesn't exists. + */ +gchar * purple_http_cookie_jar_get(PurpleHttpCookieJar *cookie_jar, + const gchar *name); + +/** + * purple_http_cookie_jar_is_empty: + * @cookie_jar: The cookie jar. + * + * Checks, if the cookie jar contains any cookies. + * + * Returns: TRUE, if cookie jar contains any cookie, FALSE otherwise. + */ +gboolean purple_http_cookie_jar_is_empty(PurpleHttpCookieJar *cookie_jar); + + +/**************************************************************************/ +/* HTTP Request API */ +/**************************************************************************/ + +/** + * purple_http_request_new: + * @url: The URL to request for, or NULL to leave empty (to be set with + * purple_http_request_set_url). + * + * Creates the new instance of HTTP request configuration. + * + * Returns: The new instance of HTTP request struct. + */ +PurpleHttpRequest * purple_http_request_new(const gchar *url); + +/** + * purple_http_request_ref: + * @request: The request. + * + * Increment the reference count. + */ +void purple_http_request_ref(PurpleHttpRequest *request); + +/** + * purple_http_request_unref: + * @request: The request. + * + * Decrement the reference count. + * + * If the reference count reaches zero, the http request struct will be freed. + * + * Returns: @request or %NULL if the reference count reached zero. + */ +PurpleHttpRequest * purple_http_request_unref(PurpleHttpRequest *request); + +/** + * purple_http_request_set_url: + * @request: The request. + * @url: The url. + * + * Sets URL for HTTP request. + */ +void purple_http_request_set_url(PurpleHttpRequest *request, const gchar *url); + +/** + * purple_http_request_set_url_printf: + * @request: The request. + * @format: The format string. + * @...: The parameters to insert into the format string. + * + * Constructs and sets an URL for HTTP request. + */ +void purple_http_request_set_url_printf(PurpleHttpRequest *request, + const gchar *format, ...) G_GNUC_PRINTF(2, 3); + +/** + * purple_http_request_get_url: + * @request: The request. + * + * Gets URL set for the HTTP request. + * + * Returns: URL set for this request. + */ +const gchar * purple_http_request_get_url(PurpleHttpRequest *request); + +/** + * purple_http_request_set_method: + * @request: The request. + * @method: The method, or NULL for default. + * + * Sets custom HTTP method used for the request. + */ +void purple_http_request_set_method(PurpleHttpRequest *request, + const gchar *method); + +/** + * purple_http_request_get_method: + * @request: The request. + * + * Gets HTTP method set for the request. + * + * Returns: The method. + */ +const gchar * purple_http_request_get_method(PurpleHttpRequest *request); + +/** + * purple_http_request_set_keepalive_pool: + * @request: The request. + * @pool: The new KeepAlive pool, or NULL to reset. + * + * Sets HTTP KeepAlive connections pool for the request. + * + * It increases pool's reference count. + */ +void +purple_http_request_set_keepalive_pool(PurpleHttpRequest *request, + PurpleHttpKeepalivePool *pool); + +/** + * purple_http_request_get_keepalive_pool: + * @request: The request. + * + * Gets HTTP KeepAlive connections pool associated with the request. + * + * It doesn't affect pool's reference count. + * + * Returns: The KeepAlive pool, used for the request. + */ +PurpleHttpKeepalivePool * +purple_http_request_get_keepalive_pool(PurpleHttpRequest *request); + +/** + * purple_http_request_set_contents: + * @request: The request. + * @contents: The contents. + * @length: The length of contents (-1 if it's a NULL-terminated string) + * + * Sets contents of HTTP request (for example, POST data). + */ +void purple_http_request_set_contents(PurpleHttpRequest *request, + const gchar *contents, int length); + +/** + * purple_http_request_set_contents_reader: + * @request: The request. + * @reader: (scope call): The reader callback. + * @contents_length: The size of all contents. + * @user_data: The user data to pass to the callback function. + * + * Sets contents reader for HTTP request, used mainly for possible large + * uploads. + */ +void purple_http_request_set_contents_reader(PurpleHttpRequest *request, + PurpleHttpContentReader reader, int contents_length, gpointer user_data); + +/** + * purple_http_request_set_response_writer: + * @request: The request. + * @writer: (scope call): The writer callback, or %NULL to remove existing. + * @user_data: The user data to pass to the callback function. + * + * Set contents writer for HTTP response. + */ +void purple_http_request_set_response_writer(PurpleHttpRequest *request, + PurpleHttpContentWriter writer, gpointer user_data); + +/** + * purple_http_request_set_timeout: + * @request: The request. + * @timeout: Time (in seconds) after that timeout will be cancelled, + * -1 for infinite time. + * + * Set maximum amount of time, that request is allowed to run. + */ +void purple_http_request_set_timeout(PurpleHttpRequest *request, int timeout); + +/** + * purple_http_request_get_timeout: + * @request: The request. + * + * Get maximum amount of time, that request is allowed to run. + * + * Returns: Timeout currently set (-1 for infinite). + */ +int purple_http_request_get_timeout(PurpleHttpRequest *request); + +/** + * purple_http_request_set_max_redirects: + * @request: The request. + * @max_redirects: Maximum amount of redirects, or -1 for unlimited. + * + * Sets maximum amount of redirects. + */ +void purple_http_request_set_max_redirects(PurpleHttpRequest *request, + int max_redirects); + +/** + * purple_http_request_get_max_redirects: + * @request: The request. + * + * Gets maximum amount of redirects. + * + * Returns: Current maximum amount of redirects (-1 for unlimited). + */ +int purple_http_request_get_max_redirects(PurpleHttpRequest *request); + +/** + * purple_http_request_set_cookie_jar: + * @request: The request. + * @cookie_jar: The cookie jar. + * + * Sets cookie jar used for the request. + */ +void purple_http_request_set_cookie_jar(PurpleHttpRequest *request, + PurpleHttpCookieJar *cookie_jar); + +/** + * purple_http_request_get_cookie_jar: + * @request: The request. + * + * Gets cookie jar used for the request. + * + * Returns: The cookie jar. + */ +PurpleHttpCookieJar * purple_http_request_get_cookie_jar( + PurpleHttpRequest *request); + +/** + * purple_http_request_set_http11: + * @request: The request. + * @http11: TRUE for HTTP/1.1, FALSE for HTTP/1.0. + * + * Sets HTTP version to use. + */ +void purple_http_request_set_http11(PurpleHttpRequest *request, + gboolean http11); + +/** + * purple_http_request_is_http11: + * @request: The request. + * + * Gets used HTTP version. + * + * Returns: TRUE, if we use HTTP/1.1, FALSE for HTTP/1.0. + */ +gboolean purple_http_request_is_http11(PurpleHttpRequest *request); + +/** + * purple_http_request_set_max_len: + * @request: The request. + * @max_len: Maximum length of response to read (-1 for the maximum + * supported amount). + * + * Sets maximum length of response content to read. + * + * Headers length doesn't count here. + * + */ +void purple_http_request_set_max_len(PurpleHttpRequest *request, int max_len); + +/** + * purple_http_request_get_max_len: + * @request: The request. + * + * Gets maximum length of response content to read. + * + * Returns: Maximum length of response to read, or -1 if unlimited. + */ +int purple_http_request_get_max_len(PurpleHttpRequest *request); + +/** + * purple_http_request_header_set: + * @request: The request. + * @key: A header to be set. + * @value: A value to set, or NULL to remove specified header. + * + * Sets (replaces, if exists) specified HTTP request header with provided value. + * + * See purple_http_request_header_add(). + */ +void purple_http_request_header_set(PurpleHttpRequest *request, + const gchar *key, const gchar *value); + +/** + * purple_http_request_header_set_printf: + * @request: The request. + * @key: A header to be set. + * @format: The format string. + * + * Constructs and sets (replaces, if exists) specified HTTP request header. + */ +void purple_http_request_header_set_printf(PurpleHttpRequest *request, + const gchar *key, const gchar *format, ...) G_GNUC_PRINTF(3, 4); + +/** + * purple_http_request_header_add: + * @request: The request. + * @key: A header to be set. + * @value: A value to set. + * + * Adds (without replacing, if exists) an HTTP request header. + * + * See purple_http_request_header_set(). + */ +void purple_http_request_header_add(PurpleHttpRequest *request, + const gchar *key, const gchar *value); + + +/**************************************************************************/ +/* HTTP Keep-Alive pool API */ +/**************************************************************************/ + +/** + * purple_http_keepalive_pool_new: + * + * Creates a new HTTP Keep-Alive pool. + */ +PurpleHttpKeepalivePool * +purple_http_keepalive_pool_new(void); + +/** + * purple_http_keepalive_pool_ref: + * @pool: The HTTP Keep-Alive pool. + * + * Increment the reference count. + */ +void +purple_http_keepalive_pool_ref(PurpleHttpKeepalivePool *pool); + +/** + * purple_http_keepalive_pool_unref: + * @pool: The HTTP Keep-Alive pool. + * + * Decrement the reference count. + * + * If the reference count reaches zero, the pool will be freed and all + * connections will be closed. + * + * Returns: @pool or %NULL if the reference count reached zero. + */ +PurpleHttpKeepalivePool * +purple_http_keepalive_pool_unref(PurpleHttpKeepalivePool *pool); + +/** + * purple_http_keepalive_pool_set_limit_per_host: + * @pool: The HTTP Keep-Alive pool. + * @limit: The new limit, 0 for unlimited. + * + * Sets maximum allowed number of connections to specific host-triple (is_ssl + + * hostname + port). + */ +void +purple_http_keepalive_pool_set_limit_per_host(PurpleHttpKeepalivePool *pool, + guint limit); + +/** + * purple_http_keepalive_pool_get_limit_per_host: + * @pool: The HTTP Keep-Alive pool. + * + * Gets maximum allowed number of connections to specific host-triple (is_ssl + + * hostname + port). + * + * Returns: The limit. + */ +guint +purple_http_keepalive_pool_get_limit_per_host(PurpleHttpKeepalivePool *pool); + + +/**************************************************************************/ +/* HTTP connection set API */ +/**************************************************************************/ + +PurpleHttpConnectionSet * +purple_http_connection_set_new(void); + +void +purple_http_connection_set_destroy(PurpleHttpConnectionSet *set); + +void +purple_http_connection_set_add(PurpleHttpConnectionSet *set, + PurpleHttpConnection *http_conn); + + +/**************************************************************************/ +/* HTTP response API */ +/**************************************************************************/ + +/** + * purple_http_response_is_successful: + * @response: The response. + * + * Checks, if HTTP request was performed successfully. + * + * Returns: TRUE, if request was performed successfully. + */ +gboolean purple_http_response_is_successful(PurpleHttpResponse *response); + +/** + * purple_http_response_get_code: + * @response: The response. + * + * Gets HTTP response code. + * + * Returns: HTTP response code. + */ +int purple_http_response_get_code(PurpleHttpResponse *response); + +/** + * purple_http_response_get_error: + * @response: The response. + * + * Gets error description. + * + * Returns: Localized error description or NULL, if there was no error. + */ +const gchar * purple_http_response_get_error(PurpleHttpResponse *response); + +/** + * purple_http_response_get_data_len: + * @response: The response. + * + * Gets HTTP response data length. + * + * Returns: Data length; + */ +gsize purple_http_response_get_data_len(PurpleHttpResponse *response); + +/** + * purple_http_response_get_data: + * @response: The response. + * @len: Return address for the size of the data. Can be NULL. + * + * Gets HTTP response data. + * + * Response data is not written, if writer callback was set for request. + * + * Returns: The data. + */ +const gchar * purple_http_response_get_data(PurpleHttpResponse *response, size_t *len); + +/** + * purple_http_response_get_all_headers: + * @response: The response. + * + * Gets all headers got with response. + * + * Returns: GList of PurpleKeyValuePair, which keys are header field + * names (gchar*) and values are its contents (gchar*). + */ +const GList * purple_http_response_get_all_headers(PurpleHttpResponse *response); + +/** + * purple_http_response_get_headers_by_name: + * @response: The response. + * @name: The name of header field. + * + * Gets all headers with specified name got with response. + * + * Returns: GList of header field records contents (gchar*). + */ +const GList * purple_http_response_get_headers_by_name( + PurpleHttpResponse *response, const gchar *name); + +/** + * purple_http_response_get_header: + * @response: The response. + * @name: The name of header field. + * + * Gets one header contents with specified name got with response. + * + * To get all headers with the same name, use + * purple_http_response_get_headers_by_name instead. + * + * Returns: Header field contents or NULL, if there is no such one. + */ +const gchar * purple_http_response_get_header(PurpleHttpResponse *response, + const gchar *name); + + +/**************************************************************************/ +/* HTTP Subsystem */ +/**************************************************************************/ + +/** + * purple_http_init: + * + * Initializes the http subsystem. + */ +void purple_http_init(void); + +/** + * purple_http_uninit: + * + * Uninitializes the http subsystem. + */ +void purple_http_uninit(void); + +G_END_DECLS + +#endif /* _PURPLE_HTTP_H_ */ diff --git a/pidgin/libpurple/protocols/facebook/Makefile b/pidgin/libpurple/protocols/facebook/Makefile new file mode 100644 index 00000000..383aace8 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/Makefile @@ -0,0 +1,890 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# pidgin/libpurple/protocols/facebook/Makefile. Generated from Makefile.in by configure. + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + + + + +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/purple-facebook +pkgincludedir = $(includedir)/purple-facebook +pkglibdir = $(libdir)/purple-facebook +pkglibexecdir = $(libexecdir)/purple-facebook +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = x86_64-pc-linux-gnu +host_triplet = x86_64-pc-linux-gnu +subdir = pidgin/libpurple/protocols/facebook +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkgdir)" +LTLIBRARIES = $(noinst_LTLIBRARIES) $(pkg_LTLIBRARIES) +am__DEPENDENCIES_1 = +libfacebook_la_DEPENDENCIES = \ + $(am__DEPENDENCIES_1) +am__libfacebook_la_SOURCES_DIST = marshal.c marshal.h api.c api.h \ + data.c data.h facebook.h facebook.c http.c http.h id.h json.c \ + json.h mqtt.c mqtt.h thrift.c thrift.h util.c util.h \ + ../../glibcompat.h ../../http.c ../../http.h \ + ../../purple-socket.h ../../purple-socket.c +am__dirstamp = $(am__leading_dot)dirstamp +am__objects_1 = libfacebook_la-marshal.lo libfacebook_la-api.lo \ + libfacebook_la-data.lo libfacebook_la-facebook.lo \ + libfacebook_la-http.lo libfacebook_la-json.lo \ + libfacebook_la-mqtt.lo libfacebook_la-thrift.lo \ + libfacebook_la-util.lo ../../libfacebook_la-http.lo \ + ../../libfacebook_la-purple-socket.lo +am_libfacebook_la_OBJECTS = $(am__objects_1) +#am_libfacebook_la_OBJECTS = $(am__objects_1) +libfacebook_la_OBJECTS = $(am_libfacebook_la_OBJECTS) +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +am__v_lt_1 = +libfacebook_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(libfacebook_la_CFLAGS) $(CFLAGS) $(libfacebook_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_libfacebook_la_rpath = -rpath $(pkgdir) +#am_libfacebook_la_rpath = +AM_V_P = $(am__v_P_$(V)) +am__v_P_ = $(am__v_P_$(AM_DEFAULT_VERBOSITY)) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I. +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ../../$(DEPDIR)/libfacebook_la-http.Plo \ + ../../$(DEPDIR)/libfacebook_la-purple-socket.Plo \ + ./$(DEPDIR)/libfacebook_la-api.Plo \ + ./$(DEPDIR)/libfacebook_la-data.Plo \ + ./$(DEPDIR)/libfacebook_la-facebook.Plo \ + ./$(DEPDIR)/libfacebook_la-http.Plo \ + ./$(DEPDIR)/libfacebook_la-json.Plo \ + ./$(DEPDIR)/libfacebook_la-marshal.Plo \ + ./$(DEPDIR)/libfacebook_la-mqtt.Plo \ + ./$(DEPDIR)/libfacebook_la-thrift.Plo \ + ./$(DEPDIR)/libfacebook_la-util.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libfacebook_la_SOURCES) +DIST_SOURCES = $(am__libfacebook_la_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = ${SHELL} '/home/james/development/purple-facebook/build-aux/missing' aclocal-1.16 +AMTAR = $${TAR-tar} +AM_DEFAULT_VERBOSITY = 0 +AR = ar +AUTOCONF = ${SHELL} '/home/james/development/purple-facebook/build-aux/missing' autoconf +AUTOHEADER = ${SHELL} '/home/james/development/purple-facebook/build-aux/missing' autoheader +AUTOMAKE = ${SHELL} '/home/james/development/purple-facebook/build-aux/missing' automake-1.16 +AWK = gawk +CC = gcc +CCDEPMODE = depmode=gcc3 +CFLAGS = -g -O2 +CPPFLAGS = +CSCOPE = cscope +CTAGS = ctags +CYGPATH_W = echo +DEFS = -DPACKAGE_NAME=\"purple-facebook\" -DPACKAGE_TARNAME=\"purple-facebook\" -DPACKAGE_VERSION=\"0.9.6\" -DPACKAGE_STRING=\"purple-facebook\ 0.9.6\" -DPACKAGE_BUGREPORT=\"https://github.com/dequis/purple-facebook/issues\" -DPACKAGE_URL=\"https://github.com/dequis/purple-facebook\" -DPACKAGE=\"purple-facebook\" -DVERSION=\"0.9.6\" -DHAVE_STDIO_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_STRINGS_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DSTDC_HEADERS=1 -DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" +DEPDIR = .deps +DLLTOOL = false +DSYMUTIL = +DUMPBIN = +ECHO_C = +ECHO_N = -n +ECHO_T = +EGREP = /usr/bin/grep -E +ETAGS = etags +EXEEXT = +FGREP = /usr/bin/grep -F +FILECMD = file +GLIB_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -pthread -I/usr/include/libmount -I/usr/include/blkid +GLIB_GENMARSHAL = /usr/bin/glib-genmarshal +GLIB_LIBS = -lgio-2.0 -lgobject-2.0 -lglib-2.0 +GREP = /usr/bin/grep +INSTALL = /usr/bin/install -c +INSTALL_DATA = ${INSTALL} -m 644 +INSTALL_PROGRAM = ${INSTALL} +INSTALL_SCRIPT = ${INSTALL} +INSTALL_STRIP_PROGRAM = $(install_sh) -c -s +JSON_CFLAGS = -I/usr/include/json-glib-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -pthread -I/usr/include/libmount -I/usr/include/blkid +JSON_LIBS = -ljson-glib-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 +LD = /usr/bin/ld -m elf_x86_64 +LDFLAGS = +LIBOBJS = +LIBS = +LIBTOOL = $(SHELL) $(top_builddir)/libtool +LIPO = +LN_S = ln -s +LTLIBOBJS = +LT_SYS_LIBRARY_PATH = +MAKEINFO = ${SHELL} '/home/james/development/purple-facebook/build-aux/missing' makeinfo +MANIFEST_TOOL = : +MKDIR_P = /usr/bin/mkdir -p +NM = /usr/bin/nm -B +NMEDIT = +OBJDUMP = objdump +OBJEXT = o +OTOOL = +OTOOL64 = +PACKAGE = purple-facebook +PACKAGE_BUGREPORT = https://github.com/dequis/purple-facebook/issues +PACKAGE_NAME = purple-facebook +PACKAGE_STRING = purple-facebook 0.9.6 +PACKAGE_TARNAME = purple-facebook +PACKAGE_URL = https://github.com/dequis/purple-facebook +PACKAGE_VERSION = 0.9.6 +PATH_SEPARATOR = : +PKG_CONFIG = /usr/bin/pkg-config +PKG_CONFIG_LIBDIR = +PKG_CONFIG_PATH = +PLUGIN_CFLAGS = -I/home/james/development/purple-facebook/./include -I/home/james/development/purple-facebook/./pidgin -I/home/james/development/purple-facebook/./pidgin/libpurple -DPURPLE_PLUGINS -include purple-compat.h +PLUGIN_LDFLAGS = -avoid-version +PURPLE_CFLAGS = -I/usr/include/libpurple -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include +PURPLE_LIBS = -lgio-2.0 -lgobject-2.0 -lglib-2.0 -ljson-glib-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -lpurple -lglib-2.0 -lz +PURPLE_PLUGINDIR = /usr/lib/x86_64-linux-gnu/purple-2 +RANLIB = ranlib +SED = /usr/bin/sed +SET_MAKE = +SHELL = /bin/bash +STRIP = strip +VERSION = 0.9.6 +ZLIB_CFLAGS = +ZLIB_LIBS = -lz +abs_builddir = /home/james/development/purple-facebook/pidgin/libpurple/protocols/facebook +abs_srcdir = /home/james/development/purple-facebook/pidgin/libpurple/protocols/facebook +abs_top_builddir = /home/james/development/purple-facebook +abs_top_srcdir = /home/james/development/purple-facebook +ac_ct_AR = ar +ac_ct_CC = gcc +ac_ct_DUMPBIN = +am__include = include +am__leading_dot = . +am__quote = +am__tar = $${TAR-tar} chof - "$$tardir" +am__untar = $${TAR-tar} xf - +bindir = ${exec_prefix}/bin +build = x86_64-pc-linux-gnu +build_alias = +build_cpu = x86_64 +build_os = linux-gnu +build_vendor = pc +builddir = . +datadir = ${datarootdir} +datarootdir = ${prefix}/share +docdir = ${datarootdir}/doc/${PACKAGE_TARNAME} +dvidir = ${docdir} +exec_prefix = ${prefix} +host = x86_64-pc-linux-gnu +host_alias = +host_cpu = x86_64 +host_os = linux-gnu +host_vendor = pc +htmldir = ${docdir} +includedir = ${prefix}/include +infodir = ${datarootdir}/info +install_sh = ${SHELL} /home/james/development/purple-facebook/build-aux/install-sh +libdir = ${exec_prefix}/lib +libexecdir = ${exec_prefix}/libexec +localedir = ${datarootdir}/locale +localstatedir = ${prefix}/var +mandir = ${datarootdir}/man +mkdir_p = $(MKDIR_P) +oldincludedir = /usr/include +pdfdir = ${docdir} +prefix = /usr/local +program_transform_name = s,x,x, +psdir = ${docdir} +runstatedir = ${localstatedir}/run +sbindir = ${exec_prefix}/sbin +sharedstatedir = ${prefix}/com +srcdir = . +sysconfdir = ${prefix}/etc +target_alias = +top_build_prefix = ../../../../ +top_builddir = ../../../.. +top_srcdir = ../../../.. +EXTRA_DIST = \ + Makefile.mingw \ + marshaller.list + +pkgdir = /usr/lib/x86_64-linux-gnu/purple-2 +FACEBOOKSOURCES = \ + marshal.c \ + marshal.h \ + api.c \ + api.h \ + data.c \ + data.h \ + facebook.h \ + facebook.c \ + http.c \ + http.h \ + id.h \ + json.c \ + json.h \ + mqtt.c \ + mqtt.h \ + thrift.c \ + thrift.h \ + util.c \ + util.h \ + ../../glibcompat.h \ + ../../http.c \ + ../../http.h \ + ../../purple-socket.h \ + ../../purple-socket.c + +CLEANFILES = \ + marshal.c \ + marshal.h + +AM_CFLAGS = $(st) +libfacebook_la_LDFLAGS = -module -avoid-version +st = +#st = -DPURPLE_STATIC_PRPL +#noinst_LTLIBRARIES = libfacebook.la +libfacebook_la_SOURCES = $(FACEBOOKSOURCES) +#libfacebook_la_SOURCES = $(FACEBOOKSOURCES) +#libfacebook_la_CFLAGS = $(AM_CFLAGS) +pkg_LTLIBRARIES = libfacebook.la +libfacebook_la_LIBADD = -lgio-2.0 -lgobject-2.0 -lglib-2.0 -ljson-glib-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -lpurple -lglib-2.0 -lz $(JSON_LIBS) +AM_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(JSON_CFLAGS) \ + $(PURPLE_CFLAGS) \ + $(ZLIB_CFLAGS) \ + $(PLUGIN_CFLAGS) \ + $(DEBUG_CFLAGS) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu pidgin/libpurple/protocols/facebook/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu pidgin/libpurple/protocols/facebook/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-pkgLTLIBRARIES: $(pkg_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(pkg_LTLIBRARIES)'; test -n "$(pkgdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkgdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkgdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkgdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkgdir)"; \ + } + +uninstall-pkgLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(pkg_LTLIBRARIES)'; test -n "$(pkgdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkgdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkgdir)/$$f"; \ + done + +clean-pkgLTLIBRARIES: + -test -z "$(pkg_LTLIBRARIES)" || rm -f $(pkg_LTLIBRARIES) + @list='$(pkg_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } +../../$(am__dirstamp): + @$(MKDIR_P) ../.. + @: > ../../$(am__dirstamp) +../../$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) ../../$(DEPDIR) + @: > ../../$(DEPDIR)/$(am__dirstamp) +../../libfacebook_la-http.lo: ../../$(am__dirstamp) \ + ../../$(DEPDIR)/$(am__dirstamp) +../../libfacebook_la-purple-socket.lo: ../../$(am__dirstamp) \ + ../../$(DEPDIR)/$(am__dirstamp) + +libfacebook.la: $(libfacebook_la_OBJECTS) $(libfacebook_la_DEPENDENCIES) $(EXTRA_libfacebook_la_DEPENDENCIES) + $(AM_V_CCLD)$(libfacebook_la_LINK) $(am_libfacebook_la_rpath) $(libfacebook_la_OBJECTS) $(libfacebook_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f ../../*.$(OBJEXT) + -rm -f ../../*.lo + +distclean-compile: + -rm -f *.tab.c + +include ../../$(DEPDIR)/libfacebook_la-http.Plo # am--include-marker +include ../../$(DEPDIR)/libfacebook_la-purple-socket.Plo # am--include-marker +include ./$(DEPDIR)/libfacebook_la-api.Plo # am--include-marker +include ./$(DEPDIR)/libfacebook_la-data.Plo # am--include-marker +include ./$(DEPDIR)/libfacebook_la-facebook.Plo # am--include-marker +include ./$(DEPDIR)/libfacebook_la-http.Plo # am--include-marker +include ./$(DEPDIR)/libfacebook_la-json.Plo # am--include-marker +include ./$(DEPDIR)/libfacebook_la-marshal.Plo # am--include-marker +include ./$(DEPDIR)/libfacebook_la-mqtt.Plo # am--include-marker +include ./$(DEPDIR)/libfacebook_la-thrift.Plo # am--include-marker +include ./$(DEPDIR)/libfacebook_la-util.Plo # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: + $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ + $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ + $(am__mv) $$depbase.Tpo $$depbase.Po +# $(AM_V_CC)source='$<' object='$@' libtool=no \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(COMPILE) -c -o $@ $< + +.c.obj: + $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ + $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ + $(am__mv) $$depbase.Tpo $$depbase.Po +# $(AM_V_CC)source='$<' object='$@' libtool=no \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: + $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ + $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ + $(am__mv) $$depbase.Tpo $$depbase.Plo +# $(AM_V_CC)source='$<' object='$@' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LTCOMPILE) -c -o $@ $< + +libfacebook_la-marshal.lo: marshal.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-marshal.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-marshal.Tpo -c -o libfacebook_la-marshal.lo `test -f 'marshal.c' || echo '$(srcdir)/'`marshal.c + $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-marshal.Tpo $(DEPDIR)/libfacebook_la-marshal.Plo +# $(AM_V_CC)source='marshal.c' object='libfacebook_la-marshal.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-marshal.lo `test -f 'marshal.c' || echo '$(srcdir)/'`marshal.c + +libfacebook_la-api.lo: api.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-api.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-api.Tpo -c -o libfacebook_la-api.lo `test -f 'api.c' || echo '$(srcdir)/'`api.c + $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-api.Tpo $(DEPDIR)/libfacebook_la-api.Plo +# $(AM_V_CC)source='api.c' object='libfacebook_la-api.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-api.lo `test -f 'api.c' || echo '$(srcdir)/'`api.c + +libfacebook_la-data.lo: data.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-data.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-data.Tpo -c -o libfacebook_la-data.lo `test -f 'data.c' || echo '$(srcdir)/'`data.c + $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-data.Tpo $(DEPDIR)/libfacebook_la-data.Plo +# $(AM_V_CC)source='data.c' object='libfacebook_la-data.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-data.lo `test -f 'data.c' || echo '$(srcdir)/'`data.c + +libfacebook_la-facebook.lo: facebook.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-facebook.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-facebook.Tpo -c -o libfacebook_la-facebook.lo `test -f 'facebook.c' || echo '$(srcdir)/'`facebook.c + $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-facebook.Tpo $(DEPDIR)/libfacebook_la-facebook.Plo +# $(AM_V_CC)source='facebook.c' object='libfacebook_la-facebook.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-facebook.lo `test -f 'facebook.c' || echo '$(srcdir)/'`facebook.c + +libfacebook_la-http.lo: http.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-http.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-http.Tpo -c -o libfacebook_la-http.lo `test -f 'http.c' || echo '$(srcdir)/'`http.c + $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-http.Tpo $(DEPDIR)/libfacebook_la-http.Plo +# $(AM_V_CC)source='http.c' object='libfacebook_la-http.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-http.lo `test -f 'http.c' || echo '$(srcdir)/'`http.c + +libfacebook_la-json.lo: json.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-json.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-json.Tpo -c -o libfacebook_la-json.lo `test -f 'json.c' || echo '$(srcdir)/'`json.c + $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-json.Tpo $(DEPDIR)/libfacebook_la-json.Plo +# $(AM_V_CC)source='json.c' object='libfacebook_la-json.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-json.lo `test -f 'json.c' || echo '$(srcdir)/'`json.c + +libfacebook_la-mqtt.lo: mqtt.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-mqtt.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-mqtt.Tpo -c -o libfacebook_la-mqtt.lo `test -f 'mqtt.c' || echo '$(srcdir)/'`mqtt.c + $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-mqtt.Tpo $(DEPDIR)/libfacebook_la-mqtt.Plo +# $(AM_V_CC)source='mqtt.c' object='libfacebook_la-mqtt.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-mqtt.lo `test -f 'mqtt.c' || echo '$(srcdir)/'`mqtt.c + +libfacebook_la-thrift.lo: thrift.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-thrift.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-thrift.Tpo -c -o libfacebook_la-thrift.lo `test -f 'thrift.c' || echo '$(srcdir)/'`thrift.c + $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-thrift.Tpo $(DEPDIR)/libfacebook_la-thrift.Plo +# $(AM_V_CC)source='thrift.c' object='libfacebook_la-thrift.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-thrift.lo `test -f 'thrift.c' || echo '$(srcdir)/'`thrift.c + +libfacebook_la-util.lo: util.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-util.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-util.Tpo -c -o libfacebook_la-util.lo `test -f 'util.c' || echo '$(srcdir)/'`util.c + $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-util.Tpo $(DEPDIR)/libfacebook_la-util.Plo +# $(AM_V_CC)source='util.c' object='libfacebook_la-util.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-util.lo `test -f 'util.c' || echo '$(srcdir)/'`util.c + +../../libfacebook_la-http.lo: ../../http.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT ../../libfacebook_la-http.lo -MD -MP -MF ../../$(DEPDIR)/libfacebook_la-http.Tpo -c -o ../../libfacebook_la-http.lo `test -f '../../http.c' || echo '$(srcdir)/'`../../http.c + $(AM_V_at)$(am__mv) ../../$(DEPDIR)/libfacebook_la-http.Tpo ../../$(DEPDIR)/libfacebook_la-http.Plo +# $(AM_V_CC)source='../../http.c' object='../../libfacebook_la-http.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o ../../libfacebook_la-http.lo `test -f '../../http.c' || echo '$(srcdir)/'`../../http.c + +../../libfacebook_la-purple-socket.lo: ../../purple-socket.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT ../../libfacebook_la-purple-socket.lo -MD -MP -MF ../../$(DEPDIR)/libfacebook_la-purple-socket.Tpo -c -o ../../libfacebook_la-purple-socket.lo `test -f '../../purple-socket.c' || echo '$(srcdir)/'`../../purple-socket.c + $(AM_V_at)$(am__mv) ../../$(DEPDIR)/libfacebook_la-purple-socket.Tpo ../../$(DEPDIR)/libfacebook_la-purple-socket.Plo +# $(AM_V_CC)source='../../purple-socket.c' object='../../libfacebook_la-purple-socket.lo' libtool=yes \ +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \ +# $(AM_V_CC_no)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o ../../libfacebook_la-purple-socket.lo `test -f '../../purple-socket.c' || echo '$(srcdir)/'`../../purple-socket.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf ../../.libs ../../_libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(pkgdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f ../../$(DEPDIR)/$(am__dirstamp) + -rm -f ../../$(am__dirstamp) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-pkgLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -f ../../$(DEPDIR)/libfacebook_la-http.Plo + -rm -f ../../$(DEPDIR)/libfacebook_la-purple-socket.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-api.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-data.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-facebook.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-http.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-json.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-marshal.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-mqtt.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-thrift.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-util.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pkgLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ../../$(DEPDIR)/libfacebook_la-http.Plo + -rm -f ../../$(DEPDIR)/libfacebook_la-purple-socket.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-api.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-data.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-facebook.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-http.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-json.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-marshal.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-mqtt.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-thrift.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-util.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkgLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-pkgLTLIBRARIES cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-pkgLTLIBRARIES install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkgLTLIBRARIES + +.PRECIOUS: Makefile + + +marshal.c: $(srcdir)/marshaller.list marshal.h + $(AM_V_GEN)echo "#include \"marshal.h\"" > $@ + $(AM_V_at)$(GLIB_GENMARSHAL) --prefix=fb_marshal --body $(srcdir)/marshaller.list >> $@ + +marshal.h: $(srcdir)/marshaller.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=fb_marshal --header $(srcdir)/marshaller.list > $@ + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/pidgin/libpurple/protocols/facebook/Makefile.am b/pidgin/libpurple/protocols/facebook/Makefile.am new file mode 100644 index 00000000..87dd0825 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/Makefile.am @@ -0,0 +1,70 @@ +EXTRA_DIST = \ + Makefile.mingw \ + marshaller.list + +pkgdir = @PURPLE_PLUGINDIR@ + +FACEBOOKSOURCES = \ + marshal.c \ + marshal.h \ + api.c \ + api.h \ + data.c \ + data.h \ + facebook.h \ + facebook.c \ + http.c \ + http.h \ + id.h \ + json.c \ + json.h \ + mqtt.c \ + mqtt.h \ + thrift.c \ + thrift.h \ + util.c \ + util.h \ + ../../glibcompat.h \ + ../../http.c \ + ../../http.h \ + ../../purple-socket.h \ + ../../purple-socket.c + +CLEANFILES = \ + marshal.c \ + marshal.h + +marshal.c: $(srcdir)/marshaller.list marshal.h + $(AM_V_GEN)echo "#include \"marshal.h\"" > $@ + $(AM_V_at)$(GLIB_GENMARSHAL) --prefix=fb_marshal --body $(srcdir)/marshaller.list >> $@ + +marshal.h: $(srcdir)/marshaller.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=fb_marshal --header $(srcdir)/marshaller.list > $@ + +AM_CFLAGS = $(st) + +libfacebook_la_LDFLAGS = -module @PLUGIN_LDFLAGS@ + +if STATIC_FACEBOOK + +st = -DPURPLE_STATIC_PRPL +noinst_LTLIBRARIES = libfacebook.la +libfacebook_la_SOURCES = $(FACEBOOKSOURCES) +libfacebook_la_CFLAGS = $(AM_CFLAGS) + +else + +st = +pkg_LTLIBRARIES = libfacebook.la +libfacebook_la_SOURCES = $(FACEBOOKSOURCES) +libfacebook_la_LIBADD = @PURPLE_LIBS@ $(JSON_LIBS) + +endif + +AM_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(JSON_CFLAGS) \ + $(PURPLE_CFLAGS) \ + $(ZLIB_CFLAGS) \ + $(PLUGIN_CFLAGS) \ + $(DEBUG_CFLAGS) diff --git a/pidgin/libpurple/protocols/facebook/Makefile.in b/pidgin/libpurple/protocols/facebook/Makefile.in new file mode 100644 index 00000000..6264df4b --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/Makefile.in @@ -0,0 +1,890 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = pidgin/libpurple/protocols/facebook +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkgdir)" +LTLIBRARIES = $(noinst_LTLIBRARIES) $(pkg_LTLIBRARIES) +am__DEPENDENCIES_1 = +@STATIC_FACEBOOK_FALSE@libfacebook_la_DEPENDENCIES = \ +@STATIC_FACEBOOK_FALSE@ $(am__DEPENDENCIES_1) +am__libfacebook_la_SOURCES_DIST = marshal.c marshal.h api.c api.h \ + data.c data.h facebook.h facebook.c http.c http.h id.h json.c \ + json.h mqtt.c mqtt.h thrift.c thrift.h util.c util.h \ + ../../glibcompat.h ../../http.c ../../http.h \ + ../../purple-socket.h ../../purple-socket.c +am__dirstamp = $(am__leading_dot)dirstamp +am__objects_1 = libfacebook_la-marshal.lo libfacebook_la-api.lo \ + libfacebook_la-data.lo libfacebook_la-facebook.lo \ + libfacebook_la-http.lo libfacebook_la-json.lo \ + libfacebook_la-mqtt.lo libfacebook_la-thrift.lo \ + libfacebook_la-util.lo ../../libfacebook_la-http.lo \ + ../../libfacebook_la-purple-socket.lo +@STATIC_FACEBOOK_FALSE@am_libfacebook_la_OBJECTS = $(am__objects_1) +@STATIC_FACEBOOK_TRUE@am_libfacebook_la_OBJECTS = $(am__objects_1) +libfacebook_la_OBJECTS = $(am_libfacebook_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libfacebook_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(libfacebook_la_CFLAGS) $(CFLAGS) $(libfacebook_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +@STATIC_FACEBOOK_FALSE@am_libfacebook_la_rpath = -rpath $(pkgdir) +@STATIC_FACEBOOK_TRUE@am_libfacebook_la_rpath = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ../../$(DEPDIR)/libfacebook_la-http.Plo \ + ../../$(DEPDIR)/libfacebook_la-purple-socket.Plo \ + ./$(DEPDIR)/libfacebook_la-api.Plo \ + ./$(DEPDIR)/libfacebook_la-data.Plo \ + ./$(DEPDIR)/libfacebook_la-facebook.Plo \ + ./$(DEPDIR)/libfacebook_la-http.Plo \ + ./$(DEPDIR)/libfacebook_la-json.Plo \ + ./$(DEPDIR)/libfacebook_la-marshal.Plo \ + ./$(DEPDIR)/libfacebook_la-mqtt.Plo \ + ./$(DEPDIR)/libfacebook_la-thrift.Plo \ + ./$(DEPDIR)/libfacebook_la-util.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libfacebook_la_SOURCES) +DIST_SOURCES = $(am__libfacebook_la_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +JSON_CFLAGS = @JSON_CFLAGS@ +JSON_LIBS = @JSON_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PLUGIN_CFLAGS = @PLUGIN_CFLAGS@ +PLUGIN_LDFLAGS = @PLUGIN_LDFLAGS@ +PURPLE_CFLAGS = @PURPLE_CFLAGS@ +PURPLE_LIBS = @PURPLE_LIBS@ +PURPLE_PLUGINDIR = @PURPLE_PLUGINDIR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +ZLIB_CFLAGS = @ZLIB_CFLAGS@ +ZLIB_LIBS = @ZLIB_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = \ + Makefile.mingw \ + marshaller.list + +pkgdir = @PURPLE_PLUGINDIR@ +FACEBOOKSOURCES = \ + marshal.c \ + marshal.h \ + api.c \ + api.h \ + data.c \ + data.h \ + facebook.h \ + facebook.c \ + http.c \ + http.h \ + id.h \ + json.c \ + json.h \ + mqtt.c \ + mqtt.h \ + thrift.c \ + thrift.h \ + util.c \ + util.h \ + ../../glibcompat.h \ + ../../http.c \ + ../../http.h \ + ../../purple-socket.h \ + ../../purple-socket.c + +CLEANFILES = \ + marshal.c \ + marshal.h + +AM_CFLAGS = $(st) +libfacebook_la_LDFLAGS = -module @PLUGIN_LDFLAGS@ +@STATIC_FACEBOOK_FALSE@st = +@STATIC_FACEBOOK_TRUE@st = -DPURPLE_STATIC_PRPL +@STATIC_FACEBOOK_TRUE@noinst_LTLIBRARIES = libfacebook.la +@STATIC_FACEBOOK_FALSE@libfacebook_la_SOURCES = $(FACEBOOKSOURCES) +@STATIC_FACEBOOK_TRUE@libfacebook_la_SOURCES = $(FACEBOOKSOURCES) +@STATIC_FACEBOOK_TRUE@libfacebook_la_CFLAGS = $(AM_CFLAGS) +@STATIC_FACEBOOK_FALSE@pkg_LTLIBRARIES = libfacebook.la +@STATIC_FACEBOOK_FALSE@libfacebook_la_LIBADD = @PURPLE_LIBS@ $(JSON_LIBS) +AM_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(JSON_CFLAGS) \ + $(PURPLE_CFLAGS) \ + $(ZLIB_CFLAGS) \ + $(PLUGIN_CFLAGS) \ + $(DEBUG_CFLAGS) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu pidgin/libpurple/protocols/facebook/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu pidgin/libpurple/protocols/facebook/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-pkgLTLIBRARIES: $(pkg_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(pkg_LTLIBRARIES)'; test -n "$(pkgdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkgdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkgdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkgdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkgdir)"; \ + } + +uninstall-pkgLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(pkg_LTLIBRARIES)'; test -n "$(pkgdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkgdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkgdir)/$$f"; \ + done + +clean-pkgLTLIBRARIES: + -test -z "$(pkg_LTLIBRARIES)" || rm -f $(pkg_LTLIBRARIES) + @list='$(pkg_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } +../../$(am__dirstamp): + @$(MKDIR_P) ../.. + @: > ../../$(am__dirstamp) +../../$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) ../../$(DEPDIR) + @: > ../../$(DEPDIR)/$(am__dirstamp) +../../libfacebook_la-http.lo: ../../$(am__dirstamp) \ + ../../$(DEPDIR)/$(am__dirstamp) +../../libfacebook_la-purple-socket.lo: ../../$(am__dirstamp) \ + ../../$(DEPDIR)/$(am__dirstamp) + +libfacebook.la: $(libfacebook_la_OBJECTS) $(libfacebook_la_DEPENDENCIES) $(EXTRA_libfacebook_la_DEPENDENCIES) + $(AM_V_CCLD)$(libfacebook_la_LINK) $(am_libfacebook_la_rpath) $(libfacebook_la_OBJECTS) $(libfacebook_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f ../../*.$(OBJEXT) + -rm -f ../../*.lo + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@../../$(DEPDIR)/libfacebook_la-http.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@../../$(DEPDIR)/libfacebook_la-purple-socket.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfacebook_la-api.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfacebook_la-data.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfacebook_la-facebook.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfacebook_la-http.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfacebook_la-json.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfacebook_la-marshal.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfacebook_la-mqtt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfacebook_la-thrift.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfacebook_la-util.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +libfacebook_la-marshal.lo: marshal.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-marshal.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-marshal.Tpo -c -o libfacebook_la-marshal.lo `test -f 'marshal.c' || echo '$(srcdir)/'`marshal.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-marshal.Tpo $(DEPDIR)/libfacebook_la-marshal.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='marshal.c' object='libfacebook_la-marshal.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-marshal.lo `test -f 'marshal.c' || echo '$(srcdir)/'`marshal.c + +libfacebook_la-api.lo: api.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-api.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-api.Tpo -c -o libfacebook_la-api.lo `test -f 'api.c' || echo '$(srcdir)/'`api.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-api.Tpo $(DEPDIR)/libfacebook_la-api.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='api.c' object='libfacebook_la-api.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-api.lo `test -f 'api.c' || echo '$(srcdir)/'`api.c + +libfacebook_la-data.lo: data.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-data.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-data.Tpo -c -o libfacebook_la-data.lo `test -f 'data.c' || echo '$(srcdir)/'`data.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-data.Tpo $(DEPDIR)/libfacebook_la-data.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='data.c' object='libfacebook_la-data.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-data.lo `test -f 'data.c' || echo '$(srcdir)/'`data.c + +libfacebook_la-facebook.lo: facebook.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-facebook.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-facebook.Tpo -c -o libfacebook_la-facebook.lo `test -f 'facebook.c' || echo '$(srcdir)/'`facebook.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-facebook.Tpo $(DEPDIR)/libfacebook_la-facebook.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='facebook.c' object='libfacebook_la-facebook.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-facebook.lo `test -f 'facebook.c' || echo '$(srcdir)/'`facebook.c + +libfacebook_la-http.lo: http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-http.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-http.Tpo -c -o libfacebook_la-http.lo `test -f 'http.c' || echo '$(srcdir)/'`http.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-http.Tpo $(DEPDIR)/libfacebook_la-http.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http.c' object='libfacebook_la-http.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-http.lo `test -f 'http.c' || echo '$(srcdir)/'`http.c + +libfacebook_la-json.lo: json.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-json.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-json.Tpo -c -o libfacebook_la-json.lo `test -f 'json.c' || echo '$(srcdir)/'`json.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-json.Tpo $(DEPDIR)/libfacebook_la-json.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='json.c' object='libfacebook_la-json.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-json.lo `test -f 'json.c' || echo '$(srcdir)/'`json.c + +libfacebook_la-mqtt.lo: mqtt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-mqtt.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-mqtt.Tpo -c -o libfacebook_la-mqtt.lo `test -f 'mqtt.c' || echo '$(srcdir)/'`mqtt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-mqtt.Tpo $(DEPDIR)/libfacebook_la-mqtt.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mqtt.c' object='libfacebook_la-mqtt.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-mqtt.lo `test -f 'mqtt.c' || echo '$(srcdir)/'`mqtt.c + +libfacebook_la-thrift.lo: thrift.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-thrift.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-thrift.Tpo -c -o libfacebook_la-thrift.lo `test -f 'thrift.c' || echo '$(srcdir)/'`thrift.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-thrift.Tpo $(DEPDIR)/libfacebook_la-thrift.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='thrift.c' object='libfacebook_la-thrift.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-thrift.lo `test -f 'thrift.c' || echo '$(srcdir)/'`thrift.c + +libfacebook_la-util.lo: util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT libfacebook_la-util.lo -MD -MP -MF $(DEPDIR)/libfacebook_la-util.Tpo -c -o libfacebook_la-util.lo `test -f 'util.c' || echo '$(srcdir)/'`util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfacebook_la-util.Tpo $(DEPDIR)/libfacebook_la-util.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='util.c' object='libfacebook_la-util.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o libfacebook_la-util.lo `test -f 'util.c' || echo '$(srcdir)/'`util.c + +../../libfacebook_la-http.lo: ../../http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT ../../libfacebook_la-http.lo -MD -MP -MF ../../$(DEPDIR)/libfacebook_la-http.Tpo -c -o ../../libfacebook_la-http.lo `test -f '../../http.c' || echo '$(srcdir)/'`../../http.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ../../$(DEPDIR)/libfacebook_la-http.Tpo ../../$(DEPDIR)/libfacebook_la-http.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../../http.c' object='../../libfacebook_la-http.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o ../../libfacebook_la-http.lo `test -f '../../http.c' || echo '$(srcdir)/'`../../http.c + +../../libfacebook_la-purple-socket.lo: ../../purple-socket.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -MT ../../libfacebook_la-purple-socket.lo -MD -MP -MF ../../$(DEPDIR)/libfacebook_la-purple-socket.Tpo -c -o ../../libfacebook_la-purple-socket.lo `test -f '../../purple-socket.c' || echo '$(srcdir)/'`../../purple-socket.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ../../$(DEPDIR)/libfacebook_la-purple-socket.Tpo ../../$(DEPDIR)/libfacebook_la-purple-socket.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../../purple-socket.c' object='../../libfacebook_la-purple-socket.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libfacebook_la_CFLAGS) $(CFLAGS) -c -o ../../libfacebook_la-purple-socket.lo `test -f '../../purple-socket.c' || echo '$(srcdir)/'`../../purple-socket.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf ../../.libs ../../_libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(pkgdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f ../../$(DEPDIR)/$(am__dirstamp) + -rm -f ../../$(am__dirstamp) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-pkgLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -f ../../$(DEPDIR)/libfacebook_la-http.Plo + -rm -f ../../$(DEPDIR)/libfacebook_la-purple-socket.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-api.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-data.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-facebook.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-http.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-json.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-marshal.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-mqtt.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-thrift.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-util.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pkgLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ../../$(DEPDIR)/libfacebook_la-http.Plo + -rm -f ../../$(DEPDIR)/libfacebook_la-purple-socket.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-api.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-data.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-facebook.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-http.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-json.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-marshal.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-mqtt.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-thrift.Plo + -rm -f ./$(DEPDIR)/libfacebook_la-util.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkgLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-pkgLTLIBRARIES cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-pkgLTLIBRARIES install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkgLTLIBRARIES + +.PRECIOUS: Makefile + + +marshal.c: $(srcdir)/marshaller.list marshal.h + $(AM_V_GEN)echo "#include \"marshal.h\"" > $@ + $(AM_V_at)$(GLIB_GENMARSHAL) --prefix=fb_marshal --body $(srcdir)/marshaller.list >> $@ + +marshal.h: $(srcdir)/marshaller.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=fb_marshal --header $(srcdir)/marshaller.list > $@ + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/pidgin/libpurple/protocols/facebook/Makefile.mingw b/pidgin/libpurple/protocols/facebook/Makefile.mingw new file mode 100644 index 00000000..181937ff --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/Makefile.mingw @@ -0,0 +1,104 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version of libfacebook +# + +PIDGIN_TREE_TOP := ../../.. +include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak + +TARGET = libfacebook +TYPE = PLUGIN + +# Static or Plugin... +ifeq ($(TYPE),STATIC) + DEFINES += -DSTATIC + DLL_INSTALL_DIR = $(PURPLE_INSTALL_DIR) +else +ifeq ($(TYPE),PLUGIN) + DLL_INSTALL_DIR = $(PURPLE_INSTALL_PLUGINS_DIR) +endif +endif + +## +## INCLUDE PATHS +## +INCLUDE_PATHS += -I. \ + -I$(GTK_TOP)/include \ + -I$(GTK_TOP)/include/glib-2.0 \ + -I$(GTK_TOP)/lib/glib-2.0/include \ + -I$(JSON_GLIB_TOP)/include/json-glib-1.0 \ + -I$(PURPLE_TOP) \ + -I$(PURPLE_TOP)/win32 \ + -I$(GPLUGIN_TOP) \ + -I$(PIDGIN_TREE_TOP) + +LIB_PATHS += -L$(GTK_TOP)/lib \ + -L$(JSON_GLIB_TOP)/lib \ + -L$(GPLUGIN_TOP) \ + -L$(PURPLE_TOP) + +## +## SOURCES, OBJECTS +## +C_SRC = \ + marshal.c \ + api.c \ + data.c \ + facebook.c \ + http.c \ + json.c \ + mqtt.c \ + thrift.c \ + util.c \ + ../../http.c \ + ../../purple-socket.c + +OBJECTS = $(C_SRC:%.c=%.o) + +## +## LIBRARIES +## +LIBS = \ + -lglib-2.0 \ + -lgio-2.0 \ + -lgobject-2.0 \ + -lws2_32 \ + -lintl \ + -ljson-glib-1.0 \ + -lz \ + -lpurple + +include $(PIDGIN_COMMON_RULES) + +## +## TARGET DEFINITIONS +## +.PHONY: all install clean + +all: $(TARGET).dll + +install: all + cp $(TARGET).dll $(DLL_INSTALL_DIR) + +$(OBJECTS): $(PURPLE_CONFIG_H) + +$(TARGET).dll: $(OBJECTS) + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll + +marshal.c: marshaller.list marshal.h + @echo "#include \"marshal.h\"" > $@ + @$(GLIB_GENMARSHAL) --prefix=fb_marshal --body marshaller.list >> $@ + +marshal.h: marshaller.list + @$(GLIB_GENMARSHAL) --prefix=fb_marshal --header marshaller.list > $@ + + +## +## CLEAN RULES +## +clean: + rm -f $(OBJECTS) marshal.c marshal.h + rm -f $(TARGET).dll + +include $(PIDGIN_COMMON_TARGETS) diff --git a/pidgin/libpurple/protocols/facebook/api.c b/pidgin/libpurple/protocols/facebook/api.c new file mode 100644 index 00000000..920cb4db --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/api.c @@ -0,0 +1,3539 @@ +/* purple + * + * 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 +#include +#include + +#include "glibcompat.h" + +#include "api.h" +#include "http.h" +#include "json.h" +#include "marshal.h" +#include "thrift.h" +#include "util.h" + +typedef struct _FbApiData FbApiData; + +enum +{ + PROP_0, + + PROP_CID, + PROP_DID, + PROP_MID, + PROP_STOKEN, + PROP_TOKEN, + PROP_UID, + + PROP_N +}; + +struct _FbApiPrivate +{ + FbMqtt *mqtt; + FbHttpConns *cons; + PurpleConnection *gc; + GHashTable *data; + gboolean retrying; + + FbId uid; + gint64 sid; + guint64 mid; + gchar *cid; + gchar *did; + gchar *stoken; + gchar *token; + + GQueue *msgs; + gboolean invisible; + guint unread; + FbId lastmid; + gchar *contacts_delta; +}; + +struct _FbApiData +{ + gpointer data; + GDestroyNotify func; +}; + +static void +fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg); + +static void +fb_api_contacts_after(FbApi *api, const gchar *cursor); + +static void +fb_api_message_send(FbApi *api, FbApiMessage *msg); + +static void +fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg); + +void +fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor); + +G_DEFINE_TYPE_WITH_CODE(FbApi, fb_api, G_TYPE_OBJECT, G_ADD_PRIVATE(FbApi)); + +static void +fb_api_set_property(GObject *obj, guint prop, const GValue *val, + GParamSpec *pspec) +{ + FbApiPrivate *priv = FB_API(obj)->priv; + + switch (prop) { + case PROP_CID: + g_free(priv->cid); + priv->cid = g_value_dup_string(val); + break; + case PROP_DID: + g_free(priv->did); + priv->did = g_value_dup_string(val); + break; + case PROP_MID: + priv->mid = g_value_get_uint64(val); + break; + case PROP_STOKEN: + g_free(priv->stoken); + priv->stoken = g_value_dup_string(val); + break; + case PROP_TOKEN: + g_free(priv->token); + priv->token = g_value_dup_string(val); + break; + case PROP_UID: + priv->uid = g_value_get_int64(val); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); + break; + } +} + +static void +fb_api_get_property(GObject *obj, guint prop, GValue *val, GParamSpec *pspec) +{ + FbApiPrivate *priv = FB_API(obj)->priv; + + switch (prop) { + case PROP_CID: + g_value_set_string(val, priv->cid); + break; + case PROP_DID: + g_value_set_string(val, priv->did); + break; + case PROP_MID: + g_value_set_uint64(val, priv->mid); + break; + case PROP_STOKEN: + g_value_set_string(val, priv->stoken); + break; + case PROP_TOKEN: + g_value_set_string(val, priv->token); + break; + case PROP_UID: + g_value_set_int64(val, priv->uid); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); + break; + } +} + + +static void +fb_api_dispose(GObject *obj) +{ + FbApiData *fata; + FbApiPrivate *priv = FB_API(obj)->priv; + GHashTableIter iter; + + fb_http_conns_cancel_all(priv->cons); + g_hash_table_iter_init(&iter, priv->data); + + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &fata)) { + fata->func(fata->data); + g_free(fata); + } + + if (G_UNLIKELY(priv->mqtt != NULL)) { + g_object_unref(priv->mqtt); + } + + fb_http_conns_free(priv->cons); + g_hash_table_destroy(priv->data); + g_queue_free_full(priv->msgs, (GDestroyNotify) fb_api_message_free); + + g_free(priv->cid); + g_free(priv->did); + g_free(priv->stoken); + g_free(priv->token); + g_free(priv->contacts_delta); +} + +static void +fb_api_class_init(FbApiClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); + GParamSpec *props[PROP_N] = {NULL}; + + gklass->set_property = fb_api_set_property; + gklass->get_property = fb_api_get_property; + gklass->dispose = fb_api_dispose; + g_type_class_add_private(klass, sizeof (FbApiPrivate)); + + /** + * FbApi:cid: + * + * The client identifier for MQTT. This value should be saved + * and loaded for persistence. + */ + props[PROP_CID] = g_param_spec_string( + "cid", + "Client ID", + "Client identifier for MQTT", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:did: + * + * The device identifier for the MQTT message queue. This value + * should be saved and loaded for persistence. + */ + props[PROP_DID] = g_param_spec_string( + "did", + "Device ID", + "Device identifier for the MQTT message queue", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:mid: + * + * The MQTT identifier. This value should be saved and loaded + * for persistence. + */ + props[PROP_MID] = g_param_spec_uint64( + "mid", + "MQTT ID", + "MQTT identifier", + 0, G_MAXUINT64, 0, + G_PARAM_READWRITE); + + /** + * FbApi:stoken: + * + * The synchronization token for the MQTT message queue. This + * value should be saved and loaded for persistence. + */ + props[PROP_STOKEN] = g_param_spec_string( + "stoken", + "Sync Token", + "Synchronization token for the MQTT message queue", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:token: + * + * The access token for authentication. This value should be + * saved and loaded for persistence. + */ + props[PROP_TOKEN] = g_param_spec_string( + "token", + "Access Token", + "Access token for authentication", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:uid: + * + * The #FbId of the user of the #FbApi. + */ + props[PROP_UID] = g_param_spec_int64( + "uid", + "User ID", + "User identifier", + 0, G_MAXINT64, 0, + G_PARAM_READWRITE); + g_object_class_install_properties(gklass, PROP_N, props); + + /** + * FbApi::auth: + * @api: The #FbApi. + * + * Emitted upon the successful completion of the authentication + * process. This is emitted as a result of #fb_api_auth(). + */ + g_signal_new("auth", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * FbApi::connect: + * @api: The #FbApi. + * + * Emitted upon the successful completion of the connection + * process. This is emitted as a result of #fb_api_connect(). + */ + g_signal_new("connect", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * FbApi::contact: + * @api: The #FbApi. + * @user: The #FbApiUser. + * + * Emitted upon the successful reply of a contact request. This + * is emitted as a result of #fb_api_contact(). + */ + g_signal_new("contact", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::contacts: + * @api: The #FbApi. + * @users: The #GSList of #FbApiUser's. + * @complete: #TRUE if the list is fetched, otherwise #FALSE. + * + * Emitted upon the successful reply of a contacts request. + * This is emitted as a result of #fb_api_contacts(). This can + * be emitted multiple times before the entire contacts list + * has been fetched. Use @complete for detecting the completion + * status of the list fetch. + */ + g_signal_new("contacts", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER_BOOLEAN, + G_TYPE_NONE, + 2, G_TYPE_POINTER, G_TYPE_BOOLEAN); + + /** + * FbApi::contacts-delta: + * @api: The #FbApi. + * @added: The #GSList of added #FbApiUser's. + * @removed: The #GSList of strings with removed user ids. + * + * Like 'contacts', but only the deltas. + */ + g_signal_new("contacts-delta", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER_POINTER, + G_TYPE_NONE, + 2, G_TYPE_POINTER, G_TYPE_POINTER); + + /** + * FbApi::error: + * @api: The #FbApi. + * @error: The #GError. + * + * Emitted whenever an error is hit within the #FbApi. This + * should disconnect the #FbApi with #fb_api_disconnect(). + */ + g_signal_new("error", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::events: + * @api: The #FbApi. + * @events: The #GSList of #FbApiEvent's. + * + * Emitted upon incoming events from the stream. + */ + g_signal_new("events", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::messages: + * @api: The #FbApi. + * @msgs: The #GSList of #FbApiMessage's. + * + * Emitted upon incoming messages from the stream. + */ + g_signal_new("messages", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::presences: + * @api: The #FbApi. + * @press: The #GSList of #FbApiPresence's. + * + * Emitted upon incoming presences from the stream. + */ + g_signal_new("presences", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::thread: + * @api: The #FbApi. + * @thrd: The #FbApiThread. + * + * Emitted upon the successful reply of a thread request. This + * is emitted as a result of #fb_api_thread(). + */ + g_signal_new("thread", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::thread-create: + * @api: The #FbApi. + * @tid: The thread #FbId. + * + * Emitted upon the successful reply of a thread creation + * request. This is emitted as a result of + * #fb_api_thread_create(). + */ + g_signal_new("thread-create", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__INT64, + G_TYPE_NONE, + 1, FB_TYPE_ID); + + /** + * FbApi::thread-kicked: + * @api: The #FbApi. + * @thrd: The #FbApiThread. + * + * Emitted upon the reply of a thread request when the user is no longer + * part of that thread. This is emitted as a result of #fb_api_thread(). + */ + g_signal_new("thread-kicked", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::threads: + * @api: The #FbApi. + * @thrds: The #GSList of #FbApiThread's. + * + * Emitted upon the successful reply of a threads request. This + * is emitted as a result of #fb_api_threads(). + */ + g_signal_new("threads", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::typing: + * @api: The #FbApi. + * @typg: The #FbApiTyping. + * + * Emitted upon an incoming typing state from the stream. + */ + g_signal_new("typing", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); +} + +static void +fb_api_init(FbApi *api) +{ + FbApiPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(api, FB_TYPE_API, FbApiPrivate); + api->priv = priv; + + priv->cons = fb_http_conns_new(); + priv->msgs = g_queue_new(); + priv->data = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, NULL); +} + +GQuark +fb_api_error_quark(void) +{ + static GQuark q = 0; + + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-api-error-quark"); + } + + return q; +} + +static void +fb_api_data_set(FbApi *api, gpointer handle, gpointer data, + GDestroyNotify func) +{ + FbApiData *fata; + FbApiPrivate *priv = api->priv; + + fata = g_new0(FbApiData, 1); + fata->data = data; + fata->func = func; + g_hash_table_replace(priv->data, handle, fata); +} + +static gpointer +fb_api_data_take(FbApi *api, gconstpointer handle) +{ + FbApiData *fata; + FbApiPrivate *priv = api->priv; + gpointer data; + + fata = g_hash_table_lookup(priv->data, handle); + + if (fata == NULL) { + return NULL; + } + + data = fata->data; + g_hash_table_remove(priv->data, handle); + g_free(fata); + return data; +} + +static gboolean +fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node) +{ + const gchar *str; + FbApiError errc = FB_API_ERROR_GENERAL; + FbApiPrivate *priv; + FbJsonValues *values; + gboolean success = TRUE; + gchar *msg; + GError *err = NULL; + gint64 code; + guint i; + JsonNode *root; + + static const gchar *exprs[] = { + "$.error.message", + "$.error.summary", + "$.error_msg", + "$.errorCode", + "$.failedSend.errorMessage", + }; + + g_return_val_if_fail(FB_IS_API(api), FALSE); + priv = api->priv; + + if (G_UNLIKELY(size == 0)) { + fb_api_error(api, FB_API_ERROR_GENERAL, _("Empty JSON data")); + return FALSE; + } + + fb_util_debug(FB_UTIL_DEBUG_INFO, "Parsing JSON: %.*s\n", + (gint) size, (const gchar *) data); + + root = fb_json_node_new(data, size, &err); + FB_API_ERROR_EMIT(api, err, return FALSE); + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.error_code"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.error.type"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.errorCode"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return FALSE + ); + + code = fb_json_values_next_int(values, 0); + str = fb_json_values_next_str(values, NULL); + + if (purple_strequal(str, "OAuthException") || (code == 401)) { + errc = FB_API_ERROR_AUTH; + success = FALSE; + + g_free(priv->stoken); + priv->stoken = NULL; + + g_free(priv->token); + priv->token = NULL; + } + + /* 509 is used for "invalid attachment id" */ + if (code == 509) { + errc = FB_API_ERROR_NONFATAL; + success = FALSE; + } + + str = fb_json_values_next_str(values, NULL); + + if (purple_strequal(str, "ERROR_QUEUE_NOT_FOUND") || + purple_strequal(str, "ERROR_QUEUE_LOST")) + { + errc = FB_API_ERROR_QUEUE; + success = FALSE; + + g_free(priv->stoken); + priv->stoken = NULL; + } + + g_object_unref(values); + + for (msg = NULL, i = 0; i < G_N_ELEMENTS(exprs); i++) { + msg = fb_json_node_get_str(root, exprs[i], NULL); + + if (msg != NULL) { + success = FALSE; + break; + } + } + + if (!success && (msg == NULL)) { + msg = g_strdup(_("Unknown error")); + } + + if (msg != NULL) { + fb_api_error(api, errc, "%s", msg); + json_node_free(root); + g_free(msg); + return FALSE; + } + + if (node != NULL) { + *node = root; + } else { + json_node_free(root); + } + + return TRUE; +} + +static gboolean +fb_api_http_chk(FbApi *api, PurpleHttpConnection *con, PurpleHttpResponse *res, + JsonNode **root) +{ + const gchar *data; + const gchar *msg; + FbApiPrivate *priv = api->priv; + gchar *emsg; + GError *err = NULL; + gint code; + gsize size; + + if (fb_http_conns_is_canceled(priv->cons)) { + return FALSE; + } + + msg = purple_http_response_get_error(res); + code = purple_http_response_get_code(res); + data = purple_http_response_get_data(res, &size); + fb_http_conns_remove(priv->cons, con); + + if (msg != NULL) { + emsg = g_strdup_printf("%s (%d)", msg, code); + } else { + emsg = g_strdup_printf("%d", code); + } + + fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Response (%p):", con); + fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Error: %s", emsg); + g_free(emsg); + + if (G_LIKELY(size > 0)) { + fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Data: %.*s", + (gint) size, data); + } + + if (fb_http_error_chk(res, &err) && (root == NULL)) { + return TRUE; + } + + /* Rudimentary check to prevent wrongful error parsing */ + if ((size < 2) || (data[0] != '{') || (data[size - 1] != '}')) { + FB_API_ERROR_EMIT(api, err, return FALSE); + } + + if (!fb_api_json_chk(api, data, size, root)) { + if (G_UNLIKELY(err != NULL)) { + g_error_free(err); + } + + return FALSE; + } + + FB_API_ERROR_EMIT(api, err, return FALSE); + return TRUE; +} + +static PurpleHttpConnection * +fb_api_http_req(FbApi *api, const gchar *url, const gchar *name, + const gchar *method, FbHttpParams *params, + PurpleHttpCallback callback) +{ + FbApiPrivate *priv = api->priv; + gchar *data; + gchar *key; + gchar *val; + GList *keys; + GList *l; + GString *gstr; + PurpleHttpConnection *ret; + PurpleHttpRequest *req; + + fb_http_params_set_str(params, "api_key", FB_API_KEY); + fb_http_params_set_str(params, "device_id", priv->did); + fb_http_params_set_str(params, "fb_api_req_friendly_name", name); + fb_http_params_set_str(params, "format", "json"); + fb_http_params_set_str(params, "method", method); + + val = fb_util_get_locale(); + fb_http_params_set_str(params, "locale", val); + g_free(val); + + req = purple_http_request_new(url); + purple_http_request_set_max_len(req, -1); + purple_http_request_set_method(req, "POST"); + + /* Ensure an old signature is not computed */ + g_hash_table_remove(params, "sig"); + + gstr = g_string_new(NULL); + keys = g_hash_table_get_keys(params); + keys = g_list_sort(keys, (GCompareFunc) g_ascii_strcasecmp); + + for (l = keys; l != NULL; l = l->next) { + key = l->data; + val = g_hash_table_lookup(params, key); + g_string_append_printf(gstr, "%s=%s", key, val); + } + + g_string_append(gstr, FB_API_SECRET); + data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, + gstr->len); + fb_http_params_set_str(params, "sig", data); + g_string_free(gstr, TRUE); + g_list_free(keys); + g_free(data); + + if (priv->token != NULL) { + data = g_strdup_printf("OAuth %s", priv->token); + purple_http_request_header_set(req, "Authorization", data); + g_free(data); + } + + purple_http_request_header_set(req, "User-Agent", FB_API_AGENT); + purple_http_request_header_set(req, "Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); + + data = fb_http_params_close(params, NULL); + purple_http_request_set_contents(req, data, -1); + ret = purple_http_request(priv->gc, req, callback, api); + fb_http_conns_add(priv->cons, ret); + purple_http_request_unref(req); + + fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Request (%p):", ret); + fb_util_debug(FB_UTIL_DEBUG_INFO, " Request URL: %s", url); + fb_util_debug(FB_UTIL_DEBUG_INFO, " Request Data: %s", data); + + g_free(data); + return ret; +} + +static PurpleHttpConnection * +fb_api_http_query(FbApi *api, gint64 query, JsonBuilder *builder, + PurpleHttpCallback hcb) +{ + const gchar *name; + FbHttpParams *prms; + gchar *json; + + switch (query) { + case FB_API_QUERY_CONTACT: + name = "UsersQuery"; + break; + case FB_API_QUERY_CONTACTS: + name = "FetchContactsFullQuery"; + break; + case FB_API_QUERY_CONTACTS_AFTER: + name = "FetchContactsFullWithAfterQuery"; + break; + case FB_API_QUERY_CONTACTS_DELTA: + name = "FetchContactsDeltaQuery"; + break; + case FB_API_QUERY_STICKER: + name = "FetchStickersWithPreviewsQuery"; + break; + case FB_API_QUERY_THREAD: + name = "ThreadQuery"; + break; + case FB_API_QUERY_SEQ_ID: + case FB_API_QUERY_THREADS: + name = "ThreadListQuery"; + break; + case FB_API_QUERY_XMA: + name = "XMAQuery"; + break; + default: + g_return_val_if_reached(NULL); + return NULL; + } + + prms = fb_http_params_new(); + json = fb_json_bldr_close(builder, JSON_NODE_OBJECT, NULL); + + fb_http_params_set_strf(prms, "query_id", "%" G_GINT64_FORMAT, query); + fb_http_params_set_str(prms, "query_params", json); + g_free(json); + + return fb_api_http_req(api, FB_API_URL_GQL, name, "get", prms, hcb); +} + +static void +fb_api_cb_http_bool(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + const gchar *hata; + FbApi *api = data; + + if (!fb_api_http_chk(api, con, res, NULL)) { + return; + } + + hata = purple_http_response_get_data(res, NULL); + + if (!purple_strequal(hata, "true")) { + fb_api_error(api, FB_API_ERROR, + _("Failed generic API operation")); + } +} + +static void +fb_api_cb_mqtt_error(FbMqtt *mqtt, GError *error, gpointer data) +{ + FbApi *api = data; + FbApiPrivate *priv = api->priv; + + if (!priv->retrying) { + priv->retrying = TRUE; + fb_util_debug_info("Attempting to reconnect the MQTT stream..."); + fb_api_connect(api, priv->invisible); + } else { + g_signal_emit_by_name(api, "error", error); + } +} + +static void +fb_api_cb_mqtt_open(FbMqtt *mqtt, gpointer data) +{ + const GByteArray *bytes; + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbThrift *thft; + GByteArray *cytes; + GError *err = NULL; + + static guint8 flags = FB_MQTT_CONNECT_FLAG_USER | + FB_MQTT_CONNECT_FLAG_PASS | + FB_MQTT_CONNECT_FLAG_CLR; + + thft = fb_thrift_new(NULL, 0); + + /* Write the client identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 1, 0); + fb_thrift_write_str(thft, priv->cid); + + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRUCT, 4, 1); + + /* Write the user identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 1, 0); + fb_thrift_write_i64(thft, priv->uid); + + /* Write the information string */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 2, 1); + fb_thrift_write_str(thft, FB_API_MQTT_AGENT); + + /* Write the UNKNOWN ("cp"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 3, 2); + fb_thrift_write_i64(thft, 23); + + /* Write the UNKNOWN ("ecp"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 4, 3); + fb_thrift_write_i64(thft, 26); + + /* Write the UNKNOWN */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 5, 4); + fb_thrift_write_i32(thft, 1); + + /* Write the UNKNOWN ("no_auto_fg"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 6, 5); + fb_thrift_write_bool(thft, TRUE); + + /* Write the visibility state */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 7, 6); + fb_thrift_write_bool(thft, !priv->invisible); + + /* Write the device identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 8, 7); + fb_thrift_write_str(thft, priv->did); + + /* Write the UNKNOWN ("fg"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 9, 8); + fb_thrift_write_bool(thft, TRUE); + + /* Write the UNKNOWN ("nwt"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 10, 9); + fb_thrift_write_i32(thft, 1); + + /* Write the UNKNOWN ("nwst"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 11, 10); + fb_thrift_write_i32(thft, 0); + + /* Write the MQTT identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 12, 11); + fb_thrift_write_i64(thft, priv->mid); + + /* Write the UNKNOWN */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_LIST, 14, 12); + fb_thrift_write_list(thft, FB_THRIFT_TYPE_I32, 0); + fb_thrift_write_stop(thft); + + /* Write the token */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 15, 14); + fb_thrift_write_str(thft, priv->token); + + /* Write the STOP for the struct */ + fb_thrift_write_stop(thft); + + bytes = fb_thrift_get_bytes(thft); + cytes = fb_util_zlib_deflate(bytes, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(thft); + return; + ); + + fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, "Writing connect"); + fb_mqtt_connect(mqtt, flags, cytes); + + g_byte_array_free(cytes, TRUE); + g_object_unref(thft); +} + +static void +fb_api_connect_queue(FbApi *api) +{ + FbApiMessage *msg; + FbApiPrivate *priv = api->priv; + gchar *json; + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_int(bldr, "delta_batch_size", 125); + fb_json_bldr_add_int(bldr, "max_deltas_able_to_process", 1250); + fb_json_bldr_add_int(bldr, "sync_api_version", 3); + fb_json_bldr_add_str(bldr, "encoding", "JSON"); + + if (priv->stoken == NULL) { + fb_json_bldr_add_int(bldr, "initial_titan_sequence_id", + priv->sid); + fb_json_bldr_add_str(bldr, "device_id", priv->did); + fb_json_bldr_add_int(bldr, "entity_fbid", priv->uid); + + fb_json_bldr_obj_begin(bldr, "queue_params"); + fb_json_bldr_add_str(bldr, "buzz_on_deltas_enabled", "false"); + + fb_json_bldr_obj_begin(bldr, "graphql_query_hashes"); + fb_json_bldr_add_str(bldr, "xma_query_id", + G_STRINGIFY(FB_API_QUERY_XMA)); + fb_json_bldr_obj_end(bldr); + + fb_json_bldr_obj_begin(bldr, "graphql_query_params"); + fb_json_bldr_obj_begin(bldr, G_STRINGIFY(FB_API_QUERY_XMA)); + fb_json_bldr_add_str(bldr, "xma_id", ""); + fb_json_bldr_obj_end(bldr); + fb_json_bldr_obj_end(bldr); + fb_json_bldr_obj_end(bldr); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/messenger_sync_create_queue", "%s", + json); + g_free(json); + return; + } + + fb_json_bldr_add_int(bldr, "last_seq_id", priv->sid); + fb_json_bldr_add_str(bldr, "sync_token", priv->stoken); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/messenger_sync_get_diffs", "%s", json); + g_signal_emit_by_name(api, "connect"); + g_free(json); + + if (!g_queue_is_empty(priv->msgs)) { + msg = g_queue_peek_head(priv->msgs); + fb_api_message_send(api, msg); + } + + if (priv->retrying) { + priv->retrying = FALSE; + fb_util_debug_info("Reconnected the MQTT stream"); + } +} + +static void +fb_api_cb_seqid(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + const gchar *str; + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.viewer.message_threads.sync_sequence_id"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, + "$.viewer.message_threads.unread_count"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + str = fb_json_values_next_str(values, "0"); + priv->sid = g_ascii_strtoll(str, NULL, 10); + priv->unread = fb_json_values_next_int(values, 0); + + if (priv->sid == 0) { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to get sync_sequence_id")); + } else { + fb_api_connect_queue(api); + } + + g_object_unref(values); + json_node_free(root); +} + +static void +fb_api_cb_mqtt_connect(FbMqtt *mqtt, gpointer data) +{ + FbApi *api = data; + FbApiPrivate *priv = api->priv; + gchar *json; + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_bool(bldr, "foreground", TRUE); + fb_json_bldr_add_int(bldr, "keepalive_timeout", FB_MQTT_KA); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/foreground_state", "%s", json); + g_free(json); + + fb_mqtt_subscribe(mqtt, + "/inbox", 0, + "/mercury", 0, + "/messaging_events", 0, + "/orca_presence", 0, + "/orca_typing_notifications", 0, + "/pp", 0, + "/t_ms", 0, + "/t_p", 0, + "/t_rtc", 0, + "/webrtc", 0, + "/webrtc_response", 0, + NULL + ); + + /* Notifications seem to lead to some sort of sending rate limit */ + fb_mqtt_unsubscribe(mqtt, "/orca_message_notifications", NULL); + + if (priv->sid == 0) { + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "1", "0"); + fb_api_http_query(api, FB_API_QUERY_SEQ_ID, bldr, + fb_api_cb_seqid); + } else { + fb_api_connect_queue(api); + } +} + +static void +fb_api_cb_publish_mark(FbApi *api, GByteArray *pload) +{ + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, "$.succeeded"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + if (!fb_json_values_next_bool(values, TRUE)) { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to mark thread as read")); + } + + g_object_unref(values); + json_node_free(root); +} + +static GSList * +fb_api_event_parse(FbApi *api, FbApiEvent *event, GSList *events, + JsonNode *root, GError **error) +{ + const gchar *str; + FbApiEvent *devent; + FbJsonValues *values; + GError *err = NULL; + guint i; + + static const struct { + FbApiEventType type; + const gchar *expr; + } evtypes[] = { + { + FB_API_EVENT_TYPE_THREAD_USER_ADDED, + "$.log_message_data.added_participants" + }, { + FB_API_EVENT_TYPE_THREAD_USER_REMOVED, + "$.log_message_data.removed_participants" + } + }; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.log_message_type"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.author"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.log_message_data.name"); + fb_json_values_update(values, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + g_object_unref(values); + return events; + } + + str = fb_json_values_next_str(values, NULL); + + if (g_strcmp0(str, "log:thread-name") == 0) { + str = fb_json_values_next_str(values, ""); + str = strrchr(str, ':'); + + if (str != NULL) { + devent = fb_api_event_dup(event, FALSE); + devent->type = FB_API_EVENT_TYPE_THREAD_TOPIC; + devent->uid = FB_ID_FROM_STR(str + 1); + devent->text = fb_json_values_next_str_dup(values, NULL); + events = g_slist_prepend(events, devent); + } + } + + g_object_unref(values); + + for (i = 0; i < G_N_ELEMENTS(evtypes); i++) { + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$"); + fb_json_values_set_array(values, FALSE, evtypes[i].expr); + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, ""); + str = strrchr(str, ':'); + + if (str != NULL) { + devent = fb_api_event_dup(event, FALSE); + devent->type = evtypes[i].type; + devent->uid = FB_ID_FROM_STR(str + 1); + events = g_slist_prepend(events, devent); + } + } + + g_object_unref(values); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + break; + } + } + + return events; +} + +static void +fb_api_cb_publish_mercury(FbApi *api, GByteArray *pload) +{ + const gchar *str; + FbApiEvent event; + FbJsonValues *values; + GError *err = NULL; + GSList *events = NULL; + JsonNode *root; + JsonNode *node; + + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.thread_fbid"); + fb_json_values_set_array(values, FALSE, "$.actions"); + + while (fb_json_values_update(values, &err)) { + fb_api_event_reset(&event, FALSE); + str = fb_json_values_next_str(values, "0"); + event.tid = FB_ID_FROM_STR(str); + + node = fb_json_values_get_root(values); + events = fb_api_event_parse(api, &event, events, node, &err); + } + + if (G_LIKELY(err == NULL)) { + events = g_slist_reverse(events); + g_signal_emit_by_name(api, "events", events); + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(events, (GDestroyNotify) fb_api_event_free); + g_object_unref(values); + json_node_free(root); + +} + +static void +fb_api_cb_publish_typing(FbApi *api, GByteArray *pload) +{ + const gchar *str; + FbApiPrivate *priv = api->priv; + FbApiTyping typg; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.type"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.sender_fbid"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.state"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + str = fb_json_values_next_str(values, NULL); + + if (g_ascii_strcasecmp(str, "typ") == 0) { + typg.uid = fb_json_values_next_int(values, 0); + + if (typg.uid != priv->uid) { + typg.state = fb_json_values_next_int(values, 0); + g_signal_emit_by_name(api, "typing", &typg); + } + } + + g_object_unref(values); + json_node_free(root); +} + +static void +fb_api_cb_publish_ms_r(FbApi *api, GByteArray *pload) +{ + FbApiMessage *msg; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.succeeded"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + if (fb_json_values_next_bool(values, TRUE)) { + /* Pop and free the successful message */ + msg = g_queue_pop_head(priv->msgs); + fb_api_message_free(msg); + + if (!g_queue_is_empty(priv->msgs)) { + msg = g_queue_peek_head(priv->msgs); + fb_api_message_send(api, msg); + } + } else { + fb_api_error(api, FB_API_ERROR_GENERAL, + "Failed to send message"); + } + + g_object_unref(values); + json_node_free(root); +} + +static gchar * +fb_api_xma_parse(FbApi *api, const gchar *body, JsonNode *root, GError **error) +{ + const gchar *str; + const gchar *url; + FbHttpParams *params; + FbJsonValues *values; + gchar *text; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.story_attachment.target.__type__.name"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.story_attachment.url"); + fb_json_values_update(values, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + g_object_unref(values); + return NULL; + } + + str = fb_json_values_next_str(values, NULL); + url = fb_json_values_next_str(values, NULL); + + if ((str == NULL) || (url == NULL)) { + text = g_strdup(_("")); + g_object_unref(values); + return text; + } + + if (purple_strequal(str, "ExternalUrl")) { + params = fb_http_params_new_parse(url, TRUE); + if (g_str_has_prefix(url, FB_API_FBRPC_PREFIX)) { + text = fb_http_params_dup_str(params, "target_url", NULL); + } else { + text = fb_http_params_dup_str(params, "u", NULL); + } + fb_http_params_free(params); + } else { + text = g_strdup(url); + } + + if (fb_http_urlcmp(body, text, FALSE)) { + g_free(text); + g_object_unref(values); + return NULL; + } + + g_object_unref(values); + return text; +} + +static GSList * +fb_api_message_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg, + GSList *msgs, const gchar *body, JsonNode *root, + GError **error) +{ + const gchar *str; + FbApiMessage *dmsg; + FbId id; + FbJsonValues *values; + gchar *xma; + GError *err = NULL; + JsonNode *node; + JsonNode *xode; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.xmaGraphQL"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.fbid"); + fb_json_values_set_array(values, FALSE, "$.attachments"); + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + id = fb_json_values_next_int(values, 0); + dmsg = fb_api_message_dup(msg, FALSE); + fb_api_attach(api, id, mid, dmsg); + continue; + } + + node = fb_json_node_new(str, -1, &err); + + if (G_UNLIKELY(err != NULL)) { + break; + } + + xode = fb_json_node_get_nth(node, 0); + xma = fb_api_xma_parse(api, body, xode, &err); + + if (xma != NULL) { + dmsg = fb_api_message_dup(msg, FALSE); + dmsg->text = xma; + msgs = g_slist_prepend(msgs, dmsg); + } + + json_node_free(node); + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + } + + g_object_unref(values); + return msgs; +} + + +static GSList * +fb_api_cb_publish_ms_new_message(FbApi *api, JsonNode *root, GSList *msgs, GError **error); + +static GSList * +fb_api_cb_publish_ms_event(FbApi *api, JsonNode *root, GSList *events, FbApiEventType type, GError **error); + +static void +fb_api_cb_publish_mst(FbThrift *thft, GError **error) +{ + if (fb_thrift_read_isstop(thft)) { + FB_API_TCHK(fb_thrift_read_stop(thft)); + } else { + FbThriftType type; + gint16 id; + + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); + FB_API_TCHK(type == FB_THRIFT_TYPE_STRING); + // FB_API_TCHK(id == 2); + FB_API_TCHK(fb_thrift_read_str(thft, NULL)); + FB_API_TCHK(fb_thrift_read_stop(thft)); + } +} + +static void +fb_api_cb_publish_ms(FbApi *api, GByteArray *pload) +{ + const gchar *data; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + FbThrift *thft; + gchar *stoken; + GError *err = NULL; + GList *elms, *l; + GSList *msgs = NULL; + GSList *events = NULL; + guint size; + JsonNode *root; + JsonNode *node; + JsonArray *arr; + + static const struct { + const gchar *member; + FbApiEventType type; + gboolean is_message; + } event_types[] = { + {"deltaNewMessage", 0, 1}, + {"deltaThreadName", FB_API_EVENT_TYPE_THREAD_TOPIC, 0}, + {"deltaParticipantsAddedToGroupThread", FB_API_EVENT_TYPE_THREAD_USER_ADDED, 0}, + {"deltaParticipantLeftGroupThread", FB_API_EVENT_TYPE_THREAD_USER_REMOVED, 0}, + }; + + /* Read identifier string (for Facebook employees) */ + thft = fb_thrift_new(pload, 0); + fb_api_cb_publish_mst(thft, &err); + size = fb_thrift_get_pos(thft); + g_object_unref(thft); + + FB_API_ERROR_EMIT(api, err, + return; + ); + + g_return_if_fail(size < pload->len); + data = (gchar *) pload->data + size; + size = pload->len - size; + + if (!fb_api_json_chk(api, data, size, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.lastIssuedSeqId"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.syncToken"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + priv->sid = fb_json_values_next_int(values, 0); + stoken = fb_json_values_next_str_dup(values, NULL); + g_object_unref(values); + + if (G_UNLIKELY(stoken != NULL)) { + g_free(priv->stoken); + priv->stoken = stoken; + g_signal_emit_by_name(api, "connect"); + json_node_free(root); + return; + } + + arr = fb_json_node_get_arr(root, "$.deltas", NULL); + elms = json_array_get_elements(arr); + + for (l = elms; l != NULL; l = l->next) { + guint i = 0; + JsonObject *o = json_node_get_object(l->data); + + for (i = 0; i < G_N_ELEMENTS(event_types); i++) { + if ((node = json_object_get_member(o, event_types[i].member))) { + if (event_types[i].is_message) { + msgs = fb_api_cb_publish_ms_new_message( + api, node, msgs, &err + ); + } else { + events = fb_api_cb_publish_ms_event( + api, node, events, event_types[i].type, &err + ); + } + } + } + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + g_list_free(elms); + json_array_unref(arr); + + if (G_LIKELY(err == NULL)) { + if (msgs) { + msgs = g_slist_reverse(msgs); + g_signal_emit_by_name(api, "messages", msgs); + } + + if (events) { + events = g_slist_reverse(events); + g_signal_emit_by_name(api, "events", events); + } + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_slist_free_full(events, (GDestroyNotify) fb_api_event_free); + json_node_free(root); +} + +static GSList * +fb_api_cb_publish_ms_new_message(FbApi *api, JsonNode *root, GSList *msgs, GError **error) +{ + const gchar *body; + const gchar *str; + GError *err = NULL; + FbApiPrivate *priv = api->priv; + FbApiMessage *dmsg; + FbApiMessage msg; + FbId id; + FbId oid; + FbJsonValues *values; + JsonNode *node; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata.offlineThreadingId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata.actorFbId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata" + ".threadKey.otherUserFbId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata" + ".threadKey.threadFbId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata.timestamp"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.body"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.stickerId"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.messageMetadata.messageId"); + + if (fb_json_values_update(values, &err)) { + id = fb_json_values_next_int(values, 0); + + /* Ignore everything but new messages */ + if (id == 0) { + goto beach; + } + + /* Ignore sequential duplicates */ + if (id == priv->lastmid) { + fb_util_debug_info("Ignoring duplicate %" FB_ID_FORMAT, id); + goto beach; + } + + priv->lastmid = id; + fb_api_message_reset(&msg, FALSE); + msg.uid = fb_json_values_next_int(values, 0); + oid = fb_json_values_next_int(values, 0); + msg.tid = fb_json_values_next_int(values, 0); + msg.tstamp = fb_json_values_next_int(values, 0); + + if (msg.uid == priv->uid) { + msg.flags |= FB_API_MESSAGE_FLAG_SELF; + + if (msg.tid == 0) { + msg.uid = oid; + } + } + + body = fb_json_values_next_str(values, NULL); + + if (body != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + dmsg->text = g_strdup(body); + msgs = g_slist_prepend(msgs, dmsg); + } + + id = fb_json_values_next_int(values, 0); + + if (id != 0) { + dmsg = fb_api_message_dup(&msg, FALSE); + fb_api_sticker(api, id, dmsg); + } + + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + goto beach; + } + + node = fb_json_values_get_root(values); + msgs = fb_api_message_parse_attach(api, str, &msg, msgs, body, + node, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + goto beach; + } + } + +beach: + g_object_unref(values); + return msgs; +} + +static GSList * +fb_api_cb_publish_ms_event(FbApi *api, JsonNode *root, GSList *events, FbApiEventType type, GError **error) +{ + FbApiEvent *event; + FbJsonValues *values = NULL; + FbJsonValues *values_inner = NULL; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata.threadKey.threadFbId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata.actorFbId"); + + switch (type) { + case FB_API_EVENT_TYPE_THREAD_TOPIC: + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.name"); + break; + + case FB_API_EVENT_TYPE_THREAD_USER_ADDED: + values_inner = fb_json_values_new(root); + + fb_json_values_add(values_inner, FB_JSON_TYPE_INT, FALSE, + "$.userFbId"); + + /* use the text field for the full name */ + fb_json_values_add(values_inner, FB_JSON_TYPE_STR, FALSE, + "$.fullName"); + + fb_json_values_set_array(values_inner, FALSE, + "$.addedParticipants"); + break; + + case FB_API_EVENT_TYPE_THREAD_USER_REMOVED: + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.leftParticipantFbId"); + + /* use the text field for the kick message */ + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.messageMetadata.adminText"); + break; + } + + fb_json_values_update(values, &err); + + event = fb_api_event_dup(NULL, FALSE); + event->type = type; + event->tid = fb_json_values_next_int(values, 0); + event->uid = fb_json_values_next_int(values, 0); + + if (type == FB_API_EVENT_TYPE_THREAD_TOPIC) { + event->text = fb_json_values_next_str_dup(values, NULL); + } else if (type == FB_API_EVENT_TYPE_THREAD_USER_REMOVED) { + /* overwrite actor with subject */ + event->uid = fb_json_values_next_int(values, 0); + event->text = fb_json_values_next_str_dup(values, NULL); + } else if (type == FB_API_EVENT_TYPE_THREAD_USER_ADDED) { + + while (fb_json_values_update(values_inner, &err)) { + FbApiEvent *devent = fb_api_event_dup(event, FALSE); + + devent->uid = fb_json_values_next_int(values_inner, 0); + devent->text = fb_json_values_next_str_dup(values_inner, NULL); + + events = g_slist_prepend(events, devent); + } + fb_api_event_free(event); + event = NULL; + g_object_unref(values_inner); + } + + g_object_unref(values); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + } else if (event) { + events = g_slist_prepend(events, event); + } + + return events; +} + +static void +fb_api_cb_publish_pt(FbThrift *thft, GSList **press, GError **error) +{ + FbApiPresence *pres; + FbThriftType type; + gint16 id; + gint32 i32; + gint64 i64; + guint i; + guint size = 0; + + /* Read identifier string (for Facebook employees) */ + FB_API_TCHK(fb_thrift_read_str(thft, NULL)); + + /* Read the full list boolean field */ + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); + FB_API_TCHK(type == FB_THRIFT_TYPE_BOOL); + FB_API_TCHK(id == 1); + FB_API_TCHK(fb_thrift_read_bool(thft, NULL)); + + /* Read the list field */ + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id)); + FB_API_TCHK(type == FB_THRIFT_TYPE_LIST); + FB_API_TCHK(id == 2); + + /* Read the list */ + FB_API_TCHK(fb_thrift_read_list(thft, &type, &size)); + FB_API_TCHK(type == FB_THRIFT_TYPE_STRUCT); + + for (i = 0; i < size; i++) { + /* Read the user identifier field */ + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); + FB_API_TCHK(type == FB_THRIFT_TYPE_I64); + FB_API_TCHK(id == 1); + FB_API_TCHK(fb_thrift_read_i64(thft, &i64)); + + /* Read the active field */ + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id)); + FB_API_TCHK(type == FB_THRIFT_TYPE_I32); + FB_API_TCHK(id == 2); + FB_API_TCHK(fb_thrift_read_i32(thft, &i32)); + + pres = fb_api_presence_dup(NULL); + pres->uid = i64; + pres->active = i32 != 0; + *press = g_slist_prepend(*press, pres); + + fb_util_debug_info("Presence: %" FB_ID_FORMAT " (%d) id: %d", + i64, i32 != 0, id); + + while (id <= 6) { + if (fb_thrift_read_isstop(thft)) { + break; + } + + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id)); + + switch (id) { + case 3: + /* Read the last active timestamp field */ + FB_API_TCHK(type == FB_THRIFT_TYPE_I64); + FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); + break; + + case 4: + /* Read the active client bits field */ + FB_API_TCHK(type == FB_THRIFT_TYPE_I16); + FB_API_TCHK(fb_thrift_read_i16(thft, NULL)); + break; + + case 5: + /* Read the VoIP compatibility bits field */ + FB_API_TCHK(type == FB_THRIFT_TYPE_I64); + FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); + break; + + case 6: + /* Unknown new field */ + FB_API_TCHK(type == FB_THRIFT_TYPE_I64); + FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); + break; + + default: + /* Try to read unknown fields as varint */ + FB_API_TCHK(type == FB_THRIFT_TYPE_I16 || + type == FB_THRIFT_TYPE_I32 || + type == FB_THRIFT_TYPE_I64); + FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); + break; + } + } + + /* Read the field stop */ + FB_API_TCHK(fb_thrift_read_stop(thft)); + } + + /* Read the field stop */ + if (fb_thrift_read_isstop(thft)) { + FB_API_TCHK(fb_thrift_read_stop(thft)); + } +} + +static void +fb_api_cb_publish_p(FbApi *api, GByteArray *pload) +{ + FbThrift *thft; + GError *err = NULL; + GSList *press = NULL; + + thft = fb_thrift_new(pload, 0); + fb_api_cb_publish_pt(thft, &press, &err); + g_object_unref(thft); + + if (G_LIKELY(err == NULL)) { + g_signal_emit_by_name(api, "presences", press); + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(press, (GDestroyNotify) fb_api_presence_free); +} + +static void +fb_api_cb_mqtt_publish(FbMqtt *mqtt, const gchar *topic, GByteArray *pload, + gpointer data) +{ + FbApi *api = data; + gboolean comp; + GByteArray *bytes; + GError *err = NULL; + guint i; + + static const struct { + const gchar *topic; + void (*func) (FbApi *api, GByteArray *pload); + } parsers[] = { + {"/mark_thread_response", fb_api_cb_publish_mark}, + {"/mercury", fb_api_cb_publish_mercury}, + {"/orca_typing_notifications", fb_api_cb_publish_typing}, + {"/send_message_response", fb_api_cb_publish_ms_r}, + {"/t_ms", fb_api_cb_publish_ms}, + {"/t_p", fb_api_cb_publish_p} + }; + + comp = fb_util_zlib_test(pload); + + if (G_LIKELY(comp)) { + bytes = fb_util_zlib_inflate(pload, &err); + FB_API_ERROR_EMIT(api, err, return); + } else { + bytes = (GByteArray *) pload; + } + + fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, + "Reading message (topic: %s)", + topic); + + for (i = 0; i < G_N_ELEMENTS(parsers); i++) { + if (g_ascii_strcasecmp(topic, parsers[i].topic) == 0) { + parsers[i].func(api, bytes); + break; + } + } + + if (G_LIKELY(comp)) { + g_byte_array_free(bytes, TRUE); + } +} + +FbApi * +fb_api_new(PurpleConnection *gc) +{ + FbApi *api; + FbApiPrivate *priv; + + api = g_object_new(FB_TYPE_API, NULL); + priv = api->priv; + + priv->gc = gc; + priv->mqtt = fb_mqtt_new(gc); + + g_signal_connect(priv->mqtt, + "connect", + G_CALLBACK(fb_api_cb_mqtt_connect), + api); + g_signal_connect(priv->mqtt, + "error", + G_CALLBACK(fb_api_cb_mqtt_error), + api); + g_signal_connect(priv->mqtt, + "open", + G_CALLBACK(fb_api_cb_mqtt_open), + api); + g_signal_connect(priv->mqtt, + "publish", + G_CALLBACK(fb_api_cb_mqtt_publish), + api); + + return api; +} + +void +fb_api_rehash(FbApi *api) +{ + FbApiPrivate *priv; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + if (priv->cid == NULL) { + priv->cid = fb_util_rand_alnum(32); + } + + if (priv->did == NULL) { + priv->did = purple_uuid_random(); + } + + if (priv->mid == 0) { + priv->mid = g_random_int(); + } + + if (strlen(priv->cid) > 20) { + priv->cid = g_realloc_n(priv->cid , 21, sizeof *priv->cid); + priv->cid[20] = 0; + } +} + +gboolean +fb_api_is_invisible(FbApi *api) +{ + FbApiPrivate *priv; + + g_return_val_if_fail(FB_IS_API(api), FALSE); + priv = api->priv; + + return priv->invisible; +} + +void +fb_api_error(FbApi *api, FbApiError error, const gchar *format, ...) +{ + GError *err; + va_list ap; + + g_return_if_fail(FB_IS_API(api)); + + va_start(ap, format); + err = g_error_new_valist(FB_API_ERROR, error, format, ap); + va_end(ap); + + fb_api_error_emit(api, err); +} + +void +fb_api_error_emit(FbApi *api, GError *error) +{ + g_return_if_fail(FB_IS_API(api)); + g_return_if_fail(error != NULL); + + g_signal_emit_by_name(api, "error", error); + g_error_free(error); +} + +static void +fb_api_cb_attach(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + const gchar *str; + FbApi *api = data; + FbApiMessage *msg; + FbJsonValues *values; + gchar *name; + GError *err = NULL; + GSList *msgs = NULL; + guint i; + JsonNode *root; + + static const gchar *imgexts[] = {".jpg", ".png", ".gif"}; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.filename"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.redirect_uri"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + msg = fb_api_data_take(api, con); + str = fb_json_values_next_str(values, NULL); + name = g_ascii_strdown(str, -1); + + for (i = 0; i < G_N_ELEMENTS(imgexts); i++) { + if (g_str_has_suffix(name, imgexts[i])) { + msg->flags |= FB_API_MESSAGE_FLAG_IMAGE; + break; + } + } + + g_free(name); + msg->text = fb_json_values_next_str_dup(values, NULL); + msgs = g_slist_prepend(msgs, msg); + + g_signal_emit_by_name(api, "messages", msgs); + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_object_unref(values); + json_node_free(root); + +} + +static void +fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg) +{ + FbHttpParams *prms; + PurpleHttpConnection *http; + + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "mid", msgid); + fb_http_params_set_strf(prms, "aid", "%" FB_ID_FORMAT, aid); + + http = fb_api_http_req(api, FB_API_URL_ATTACH, "getAttachment", + "messaging.getAttachment", prms, + fb_api_cb_attach); + fb_api_data_set(api, http, msg, (GDestroyNotify) fb_api_message_free); +} + +static void +fb_api_cb_auth(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.access_token"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + g_free(priv->token); + priv->token = fb_json_values_next_str_dup(values, NULL); + priv->uid = fb_json_values_next_int(values, 0); + + g_signal_emit_by_name(api, "auth"); + g_object_unref(values); + json_node_free(root); +} + +void +fb_api_auth(FbApi *api, const gchar *user, const gchar *pass) +{ + FbHttpParams *prms; + + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "email", user); + fb_http_params_set_str(prms, "password", pass); + fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login", + prms, fb_api_cb_auth); +} + +static gchar * +fb_api_user_icon_checksum(gchar *icon) +{ + gchar *csum; + FbHttpParams *prms; + + if (G_UNLIKELY(icon == NULL)) { + return NULL; + } + + prms = fb_http_params_new_parse(icon, TRUE); + csum = fb_http_params_dup_str(prms, "oh", NULL); + fb_http_params_free(prms); + + if (G_UNLIKELY(csum == NULL)) { + /* Revert to the icon URL as the unique checksum */ + csum = g_strdup(icon); + } + + return csum; +} + +static void +fb_api_cb_contact(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + const gchar *str; + FbApi *api = data; + FbApiUser user; + FbJsonValues *values; + GError *err = NULL; + JsonNode *node; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + node = fb_json_node_get_nth(root, 0); + + if (node == NULL) { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to obtain contact information")); + json_node_free(root); + return; + } + + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.name"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.profile_pic_large.uri"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + fb_api_user_reset(&user, FALSE); + str = fb_json_values_next_str(values, "0"); + user.uid = FB_ID_FROM_STR(str); + user.name = fb_json_values_next_str_dup(values, NULL); + user.icon = fb_json_values_next_str_dup(values, NULL); + + user.csum = fb_api_user_icon_checksum(user.icon); + + g_signal_emit_by_name(api, "contact", &user); + fb_api_user_reset(&user, TRUE); + g_object_unref(values); + json_node_free(root); +} + +void +fb_api_contact(FbApi *api, FbId uid) +{ + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid); + fb_json_bldr_arr_end(bldr); + + fb_json_bldr_add_str(bldr, "1", "true"); + fb_api_http_query(api, FB_API_QUERY_CONTACT, bldr, fb_api_cb_contact); +} + +static GSList * +fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users) +{ + const gchar *str; + FbApiPrivate *priv = api->priv; + FbApiUser *user; + FbId uid; + FbJsonValues *values; + gboolean is_array; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.represented_profile.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.represented_profile.friendship_status"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.structured_name.text"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.hugePictureUrl.uri"); + + is_array = (JSON_NODE_TYPE(root) == JSON_NODE_ARRAY); + + if (is_array) { + fb_json_values_set_array(values, FALSE, "$"); + } + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, "0"); + uid = FB_ID_FROM_STR(str); + str = fb_json_values_next_str(values, NULL); + + if ((!purple_strequal(str, "ARE_FRIENDS") && + (uid != priv->uid)) || (uid == 0)) + { + if (!is_array) { + break; + } + continue; + } + + user = fb_api_user_dup(NULL, FALSE); + user->uid = uid; + user->name = fb_json_values_next_str_dup(values, NULL); + user->icon = fb_json_values_next_str_dup(values, NULL); + + user->csum = fb_api_user_icon_checksum(user->icon); + + users = g_slist_prepend(users, user); + + if (!is_array) { + break; + } + } + + g_object_unref(values); + + return users; +} + +/* base64(contact:::) */ +static GSList * +fb_api_cb_contacts_parse_removed(FbApi *api, JsonNode *node, GSList *users) +{ + gsize len; + char **split; + char *decoded = (char *) g_base64_decode(json_node_get_string(node), &len); + + g_return_val_if_fail(decoded[len] == '\0', users); + g_return_val_if_fail(len == strlen(decoded), users); + g_return_val_if_fail(g_str_has_prefix(decoded, "contact:"), users); + + split = g_strsplit_set(decoded, ":", 4); + + g_return_val_if_fail(g_strv_length(split) == 4, users); + + users = g_slist_prepend(users, g_strdup(split[2])); + + g_strfreev(split); + g_free(decoded); + + return users; +} + +static void +fb_api_cb_contacts(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + const gchar *cursor; + const gchar *delta_cursor; + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + gboolean complete; + gboolean is_delta; + GError *err = NULL; + GList *l; + GSList *users = NULL; + JsonNode *root; + JsonNode *croot; + JsonNode *node; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + croot = fb_json_node_get(root, "$.viewer.messenger_contacts.deltas", NULL); + is_delta = (croot != NULL); + + if (!is_delta) { + croot = fb_json_node_get(root, "$.viewer.messenger_contacts", NULL); + node = fb_json_node_get(croot, "$.nodes", NULL); + users = fb_api_cb_contacts_nodes(api, node, users); + json_node_free(node); + + } else { + GSList *added = NULL; + GSList *removed = NULL; + JsonArray *arr = fb_json_node_get_arr(croot, "$.nodes", NULL); + GList *elms = json_array_get_elements(arr); + + for (l = elms; l != NULL; l = l->next) { + if ((node = fb_json_node_get(l->data, "$.added", NULL))) { + added = fb_api_cb_contacts_nodes(api, node, added); + json_node_free(node); + } + + if ((node = fb_json_node_get(l->data, "$.removed", NULL))) { + removed = fb_api_cb_contacts_parse_removed(api, node, removed); + json_node_free(node); + } + } + + g_signal_emit_by_name(api, "contacts-delta", added, removed); + + g_slist_free_full(added, (GDestroyNotify) fb_api_user_free); + g_slist_free_full(removed, (GDestroyNotify) g_free); + + g_list_free(elms); + json_array_unref(arr); + } + + values = fb_json_values_new(croot); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, + "$.page_info.has_next_page"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.page_info.delta_cursor"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.page_info.end_cursor"); + fb_json_values_update(values, NULL); + + complete = !fb_json_values_next_bool(values, FALSE); + + delta_cursor = fb_json_values_next_str(values, NULL); + + cursor = fb_json_values_next_str(values, NULL); + + if (G_UNLIKELY(err == NULL)) { + if (is_delta || complete) { + g_free(priv->contacts_delta); + priv->contacts_delta = g_strdup(is_delta ? cursor : delta_cursor); + } + + if (users || (complete && !is_delta)) { + g_signal_emit_by_name(api, "contacts", users, complete); + } + + if (!complete) { + fb_api_contacts_after(api, cursor); + } + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(users, (GDestroyNotify) fb_api_user_free); + g_object_unref(values); + + json_node_free(croot); + json_node_free(root); +} + +void +fb_api_contacts(FbApi *api) +{ + FbApiPrivate *priv; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + if (priv->contacts_delta) { + fb_api_contacts_delta(api, priv->contacts_delta); + return; + } + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_str(bldr, NULL, "user"); + fb_json_bldr_arr_end(bldr); + + fb_json_bldr_add_str(bldr, "1", G_STRINGIFY(FB_API_CONTACTS_COUNT)); + fb_api_http_query(api, FB_API_QUERY_CONTACTS, bldr, + fb_api_cb_contacts); +} + +static void +fb_api_contacts_after(FbApi *api, const gchar *cursor) +{ + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_str(bldr, NULL, "user"); + fb_json_bldr_arr_end(bldr); + + fb_json_bldr_add_str(bldr, "1", cursor); + fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT)); + fb_api_http_query(api, FB_API_QUERY_CONTACTS_AFTER, bldr, + fb_api_cb_contacts); +} + +void +fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor) +{ + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + + fb_json_bldr_add_str(bldr, "0", delta_cursor); + + fb_json_bldr_arr_begin(bldr, "1"); + fb_json_bldr_add_str(bldr, NULL, "user"); + fb_json_bldr_arr_end(bldr); + + fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT)); + fb_api_http_query(api, FB_API_QUERY_CONTACTS_DELTA, bldr, + fb_api_cb_contacts); +} + +void +fb_api_connect(FbApi *api, gboolean invisible) +{ + FbApiPrivate *priv; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + priv->invisible = invisible; + fb_mqtt_open(priv->mqtt, FB_MQTT_HOST, FB_MQTT_PORT); +} + +void +fb_api_disconnect(FbApi *api) +{ + FbApiPrivate *priv; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + fb_mqtt_disconnect(priv->mqtt); +} + +static void +fb_api_message_send(FbApi *api, FbApiMessage *msg) +{ + const gchar *tpfx; + FbApiPrivate *priv = api->priv; + FbId id; + FbId mid; + gchar *json; + JsonBuilder *bldr; + + mid = FB_API_MSGID(g_get_real_time() / 1000, g_random_int()); + priv->lastmid = mid; + + if (msg->tid != 0) { + tpfx = "tfbid_"; + id = msg->tid; + } else { + tpfx = ""; + id = msg->uid; + } + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "body", msg->text); + fb_json_bldr_add_strf(bldr, "msgid", "%" FB_ID_FORMAT, mid); + fb_json_bldr_add_strf(bldr, "sender_fbid", "%" FB_ID_FORMAT, priv->uid); + fb_json_bldr_add_strf(bldr, "to", "%s%" FB_ID_FORMAT, tpfx, id); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/send_message2", "%s", json); + g_free(json); +} + +void +fb_api_message(FbApi *api, FbId id, gboolean thread, const gchar *text) +{ + FbApiMessage *msg; + FbApiPrivate *priv; + gboolean empty; + + g_return_if_fail(FB_IS_API(api)); + g_return_if_fail(text != NULL); + priv = api->priv; + + msg = fb_api_message_dup(NULL, FALSE); + msg->text = g_strdup(text); + + if (thread) { + msg->tid = id; + } else { + msg->uid = id; + } + + empty = g_queue_is_empty(priv->msgs); + g_queue_push_tail(priv->msgs, msg); + + if (empty && fb_mqtt_connected(priv->mqtt, FALSE)) { + fb_api_message_send(api, msg); + } +} + +void +fb_api_publish(FbApi *api, const gchar *topic, const gchar *format, ...) +{ + FbApiPrivate *priv; + GByteArray *bytes; + GByteArray *cytes; + gchar *msg; + GError *err = NULL; + va_list ap; + + g_return_if_fail(FB_IS_API(api)); + g_return_if_fail(topic != NULL); + g_return_if_fail(format != NULL); + priv = api->priv; + + va_start(ap, format); + msg = g_strdup_vprintf(format, ap); + va_end(ap); + + bytes = g_byte_array_new_take((guint8 *) msg, strlen(msg)); + cytes = fb_util_zlib_deflate(bytes, &err); + + FB_API_ERROR_EMIT(api, err, + g_byte_array_free(bytes, TRUE); + return; + ); + + fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, + "Writing message (topic: %s)", + topic); + + fb_mqtt_publish(priv->mqtt, topic, cytes); + g_byte_array_free(cytes, TRUE); + g_byte_array_free(bytes, TRUE); +} + +void +fb_api_read(FbApi *api, FbId id, gboolean thread) +{ + const gchar *key; + FbApiPrivate *priv; + gchar *json; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_bool(bldr, "state", TRUE); + fb_json_bldr_add_int(bldr, "syncSeqId", priv->sid); + fb_json_bldr_add_str(bldr, "mark", "read"); + + key = thread ? "threadFbId" : "otherUserFbId"; + fb_json_bldr_add_strf(bldr, key, "%" FB_ID_FORMAT, id); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/mark_thread", "%s", json); + g_free(json); +} + +static GSList * +fb_api_cb_unread_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg, + GSList *msgs, JsonNode *root, GError **error) +{ + const gchar *str; + FbApiMessage *dmsg; + FbId id; + FbJsonValues *values; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.attachment_fbid"); + fb_json_values_set_array(values, FALSE, "$.blob_attachments"); + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, NULL); + id = FB_ID_FROM_STR(str); + dmsg = fb_api_message_dup(msg, FALSE); + fb_api_attach(api, id, mid, dmsg); + } + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + } + + g_object_unref(values); + return msgs; +} + +static void +fb_api_cb_unread_msgs(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + const gchar *body; + const gchar *str; + FbApi *api = data; + FbApiMessage *dmsg; + FbApiMessage msg; + FbId id; + FbId tid; + FbJsonValues *values; + gchar *xma; + GError *err = NULL; + GSList *msgs = NULL; + JsonNode *node; + JsonNode *root; + JsonNode *xode; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + node = fb_json_node_get_nth(root, 0); + + if (node == NULL) { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to obtain unread messages")); + json_node_free(root); + return; + } + + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.thread_fbid"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + return; + ); + + fb_api_message_reset(&msg, FALSE); + str = fb_json_values_next_str(values, "0"); + tid = FB_ID_FROM_STR(str); + g_object_unref(values); + + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.unread"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.message_sender.messaging_actor.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.message.text"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.timestamp_precise"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.sticker.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.message_id"); + fb_json_values_set_array(values, FALSE, "$.messages.nodes"); + + while (fb_json_values_update(values, &err)) { + if (!fb_json_values_next_bool(values, FALSE)) { + continue; + } + + str = fb_json_values_next_str(values, "0"); + body = fb_json_values_next_str(values, NULL); + + fb_api_message_reset(&msg, FALSE); + msg.uid = FB_ID_FROM_STR(str); + msg.tid = tid; + + str = fb_json_values_next_str(values, "0"); + msg.tstamp = g_ascii_strtoll(str, NULL, 10); + + if (body != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + dmsg->text = g_strdup(body); + msgs = g_slist_prepend(msgs, dmsg); + } + + str = fb_json_values_next_str(values, NULL); + + if (str != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + id = FB_ID_FROM_STR(str); + fb_api_sticker(api, id, dmsg); + } + + node = fb_json_values_get_root(values); + xode = fb_json_node_get(node, "$.extensible_attachment", NULL); + + if (xode != NULL) { + xma = fb_api_xma_parse(api, body, xode, &err); + + if (xma != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + dmsg->text = xma; + msgs = g_slist_prepend(msgs, dmsg); + } + + json_node_free(xode); + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + continue; + } + + msgs = fb_api_cb_unread_parse_attach(api, str, &msg, msgs, + node, &err); + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + if (G_UNLIKELY(err == NULL)) { + msgs = g_slist_reverse(msgs); + g_signal_emit_by_name(api, "messages", msgs); + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_object_unref(values); + json_node_free(root); +} + +static void +fb_api_cb_unread(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + const gchar *id; + FbApi *api = data; + FbJsonValues *values; + GError *err = NULL; + gint64 count; + JsonBuilder *bldr; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.unread_count"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.other_user_id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.thread_fbid"); + fb_json_values_set_array(values, FALSE, "$.viewer.message_threads" + ".nodes"); + + while (fb_json_values_update(values, &err)) { + count = fb_json_values_next_int(values, -5); + + if (count < 1) { + continue; + } + + id = fb_json_values_next_str(values, NULL); + + if (id == NULL) { + id = fb_json_values_next_str(values, "0"); + } + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_str(bldr, NULL, id); + fb_json_bldr_arr_end(bldr); + + fb_json_bldr_add_str(bldr, "10", "true"); + fb_json_bldr_add_str(bldr, "11", "true"); + fb_json_bldr_add_int(bldr, "12", count); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREAD, bldr, + fb_api_cb_unread_msgs); + } + + if (G_UNLIKELY(err != NULL)) { + fb_api_error_emit(api, err); + } + + g_object_unref(values); + json_node_free(root); +} + +void +fb_api_unread(FbApi *api) +{ + FbApiPrivate *priv; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + if (priv->unread < 1) { + return; + } + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "2", "true"); + fb_json_bldr_add_int(bldr, "1", priv->unread); + fb_json_bldr_add_str(bldr, "12", "true"); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, + fb_api_cb_unread); +} + +static void +fb_api_cb_sticker(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + FbApi *api = data; + FbApiMessage *msg; + FbJsonValues *values; + GError *err = NULL; + GSList *msgs = NULL; + JsonNode *node; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + node = fb_json_node_get_nth(root, 0); + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.thread_image.uri"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + msg = fb_api_data_take(api, con); + msg->flags |= FB_API_MESSAGE_FLAG_IMAGE; + msg->text = fb_json_values_next_str_dup(values, NULL); + msgs = g_slist_prepend(msgs, msg); + + g_signal_emit_by_name(api, "messages", msgs); + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_object_unref(values); + json_node_free(root); +} + +static void +fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg) +{ + JsonBuilder *bldr; + PurpleHttpConnection *http; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, sid); + fb_json_bldr_arr_end(bldr); + + http = fb_api_http_query(api, FB_API_QUERY_STICKER, bldr, + fb_api_cb_sticker); + fb_api_data_set(api, http, msg, (GDestroyNotify) fb_api_message_free); +} + +static gboolean +fb_api_thread_parse(FbApi *api, FbApiThread *thrd, JsonNode *root, + GError **error) +{ + const gchar *str; + FbApiPrivate *priv = api->priv; + FbApiUser *user; + FbId uid; + FbJsonValues *values; + gboolean haself = FALSE; + guint num_users = 0; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.thread_fbid"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.name"); + fb_json_values_update(values, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + g_object_unref(values); + return FALSE; + } + + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + g_object_unref(values); + return FALSE; + } + + thrd->tid = FB_ID_FROM_STR(str); + thrd->topic = fb_json_values_next_str_dup(values, NULL); + g_object_unref(values); + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.messaging_actor.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.messaging_actor.name"); + fb_json_values_set_array(values, TRUE, "$.all_participants.nodes"); + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, "0"); + uid = FB_ID_FROM_STR(str); + num_users++; + + if (uid != priv->uid) { + user = fb_api_user_dup(NULL, FALSE); + user->uid = uid; + user->name = fb_json_values_next_str_dup(values, NULL); + thrd->users = g_slist_prepend(thrd->users, user); + } else { + haself = TRUE; + } + } + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + fb_api_thread_reset(thrd, TRUE); + g_object_unref(values); + return FALSE; + } + + if (num_users < 2 || !haself) { + g_object_unref(values); + return FALSE; + } + + g_object_unref(values); + return TRUE; +} + +static void +fb_api_cb_thread(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + FbApi *api = data; + FbApiThread thrd; + GError *err = NULL; + JsonNode *node; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + node = fb_json_node_get_nth(root, 0); + + if (node == NULL) { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to obtain thread information")); + json_node_free(root); + return; + } + + fb_api_thread_reset(&thrd, FALSE); + + if (!fb_api_thread_parse(api, &thrd, node, &err)) { + if (G_LIKELY(err == NULL)) { + if (thrd.tid) { + g_signal_emit_by_name(api, "thread-kicked", &thrd); + } else { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to parse thread information")); + } + } else { + fb_api_error_emit(api, err); + } + } else { + g_signal_emit_by_name(api, "thread", &thrd); + } + + fb_api_thread_reset(&thrd, TRUE); + json_node_free(root); +} + +void +fb_api_thread(FbApi *api, FbId tid) +{ + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, tid); + fb_json_bldr_arr_end(bldr); + + fb_json_bldr_add_str(bldr, "10", "false"); + fb_json_bldr_add_str(bldr, "11", "false"); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREAD, bldr, fb_api_cb_thread); +} + +static void +fb_api_cb_thread_create(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + const gchar *str; + FbApi *api = data; + FbId tid; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.id"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + str = fb_json_values_next_str(values, "0"); + tid = FB_ID_FROM_STR(str); + g_signal_emit_by_name(api, "thread-create", tid); + + g_object_unref(values); + json_node_free(root); +} + +void +fb_api_thread_create(FbApi *api, GSList *uids) +{ + FbApiPrivate *priv; + FbHttpParams *prms; + FbId *uid; + gchar *json; + GSList *l; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + g_warn_if_fail(g_slist_length(uids) > 1); + priv = api->priv; + + bldr = fb_json_bldr_new(JSON_NODE_ARRAY); + fb_json_bldr_obj_begin(bldr, NULL); + fb_json_bldr_add_str(bldr, "type", "id"); + fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, priv->uid); + fb_json_bldr_obj_end(bldr); + + for (l = uids; l != NULL; l = l->next) { + uid = l->data; + fb_json_bldr_obj_begin(bldr, NULL); + fb_json_bldr_add_str(bldr, "type", "id"); + fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, *uid); + fb_json_bldr_obj_end(bldr); + } + + json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "recipients", json); + fb_api_http_req(api, FB_API_URL_THREADS, "createGroup", "POST", + prms, fb_api_cb_thread_create); + g_free(json); +} + +void +fb_api_thread_invite(FbApi *api, FbId tid, FbId uid) +{ + FbHttpParams *prms; + gchar *json; + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_ARRAY); + fb_json_bldr_obj_begin(bldr, NULL); + fb_json_bldr_add_str(bldr, "type", "id"); + fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, uid); + fb_json_bldr_obj_end(bldr); + json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); + + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "to", json); + fb_http_params_set_strf(prms, "id", "t_%" FB_ID_FORMAT, tid); + fb_api_http_req(api, FB_API_URL_PARTS, "addMembers", "POST", + prms, fb_api_cb_http_bool); + g_free(json); +} + +void +fb_api_thread_remove(FbApi *api, FbId tid, FbId uid) +{ + FbApiPrivate *priv; + FbHttpParams *prms; + gchar *json; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + prms = fb_http_params_new(); + fb_http_params_set_strf(prms, "id", "t_%" FB_ID_FORMAT, tid); + + if (uid == 0) { + uid = priv->uid; + } + + if (uid != priv->uid) { + bldr = fb_json_bldr_new(JSON_NODE_ARRAY); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid); + json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); + fb_http_params_set_str(prms, "to", json); + g_free(json); + } + + fb_api_http_req(api, FB_API_URL_PARTS, "removeMembers", "DELETE", + prms, fb_api_cb_http_bool); +} + +void +fb_api_thread_topic(FbApi *api, FbId tid, const gchar *topic) +{ + FbHttpParams *prms; + + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "name", topic); + fb_http_params_set_int(prms, "tid", tid); + fb_api_http_req(api, FB_API_URL_TOPIC, "setThreadName", + "messaging.setthreadname", prms, + fb_api_cb_http_bool); +} + +static void +fb_api_cb_threads(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + FbApi *api = data; + FbApiThread *dthrd; + FbApiThread thrd; + GError *err = NULL; + GList *elms; + GList *l; + GSList *thrds = NULL; + JsonArray *arr; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + arr = fb_json_node_get_arr(root, "$.viewer.message_threads.nodes", + &err); + FB_API_ERROR_EMIT(api, err, + json_node_free(root); + return; + ); + + elms = json_array_get_elements(arr); + + for (l = elms; l != NULL; l = l->next) { + fb_api_thread_reset(&thrd, FALSE); + + if (fb_api_thread_parse(api, &thrd, l->data, &err)) { + dthrd = fb_api_thread_dup(&thrd, FALSE); + thrds = g_slist_prepend(thrds, dthrd); + } else { + fb_api_thread_reset(&thrd, TRUE); + } + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + if (G_LIKELY(err == NULL)) { + thrds = g_slist_reverse(thrds); + g_signal_emit_by_name(api, "threads", thrds); + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(thrds, (GDestroyNotify) fb_api_thread_free); + g_list_free(elms); + json_array_unref(arr); + json_node_free(root); +} + +void +fb_api_threads(FbApi *api) +{ + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "2", "true"); + fb_json_bldr_add_str(bldr, "12", "false"); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, fb_api_cb_threads); +} + +void +fb_api_typing(FbApi *api, FbId uid, gboolean state) +{ + gchar *json; + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_int(bldr, "state", state != 0); + fb_json_bldr_add_strf(bldr, "to", "%" FB_ID_FORMAT, uid); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/typing", "%s", json); + g_free(json); +} + +FbApiEvent * +fb_api_event_dup(const FbApiEvent *event, gboolean deep) +{ + FbApiEvent *ret; + + if (event == NULL) { + return g_new0(FbApiEvent, 1); + } + + ret = g_memdup2(event, sizeof *event); + + if (deep) { + ret->text = g_strdup(event->text); + } + + return ret; +} + +void +fb_api_event_reset(FbApiEvent *event, gboolean deep) +{ + g_return_if_fail(event != NULL); + + if (deep) { + g_free(event->text); + } + + memset(event, 0, sizeof *event); +} + +void +fb_api_event_free(FbApiEvent *event) +{ + if (G_LIKELY(event != NULL)) { + g_free(event->text); + g_free(event); + } +} + +FbApiMessage * +fb_api_message_dup(const FbApiMessage *msg, gboolean deep) +{ + FbApiMessage *ret; + + if (msg == NULL) { + return g_new0(FbApiMessage, 1); + } + + ret = g_memdup2(msg, sizeof *msg); + + if (deep) { + ret->text = g_strdup(msg->text); + } + + return ret; +} + +void +fb_api_message_reset(FbApiMessage *msg, gboolean deep) +{ + g_return_if_fail(msg != NULL); + + if (deep) { + g_free(msg->text); + } + + memset(msg, 0, sizeof *msg); +} + +void +fb_api_message_free(FbApiMessage *msg) +{ + if (G_LIKELY(msg != NULL)) { + g_free(msg->text); + g_free(msg); + } +} + +FbApiPresence * +fb_api_presence_dup(const FbApiPresence *pres) +{ + if (pres == NULL) { + return g_new0(FbApiPresence, 1); + } + + return g_memdup2(pres, sizeof *pres); +} + +void +fb_api_presence_reset(FbApiPresence *pres) +{ + g_return_if_fail(pres != NULL); + memset(pres, 0, sizeof *pres); +} + +void +fb_api_presence_free(FbApiPresence *pres) +{ + if (G_LIKELY(pres != NULL)) { + g_free(pres); + } +} + +FbApiThread * +fb_api_thread_dup(const FbApiThread *thrd, gboolean deep) +{ + FbApiThread *ret; + FbApiUser *user; + GSList *l; + + if (thrd == NULL) { + return g_new0(FbApiThread, 1); + } + + ret = g_memdup2(thrd, sizeof *thrd); + + if (deep) { + ret->users = NULL; + + for (l = thrd->users; l != NULL; l = l->next) { + user = fb_api_user_dup(l->data, TRUE); + ret->users = g_slist_prepend(ret->users, user); + } + + ret->topic = g_strdup(thrd->topic); + ret->users = g_slist_reverse(ret->users); + } + + return ret; +} + +void +fb_api_thread_reset(FbApiThread *thrd, gboolean deep) +{ + g_return_if_fail(thrd != NULL); + + if (deep) { + g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free); + g_free(thrd->topic); + } + + memset(thrd, 0, sizeof *thrd); +} + +void +fb_api_thread_free(FbApiThread *thrd) +{ + if (G_LIKELY(thrd != NULL)) { + g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free); + g_free(thrd->topic); + g_free(thrd); + } +} + +FbApiTyping * +fb_api_typing_dup(const FbApiTyping *typg) +{ + if (typg == NULL) { + return g_new0(FbApiTyping, 1); + } + + return g_memdup2(typg, sizeof *typg); +} + +void +fb_api_typing_reset(FbApiTyping *typg) +{ + g_return_if_fail(typg != NULL); + memset(typg, 0, sizeof *typg); +} + +void +fb_api_typing_free(FbApiTyping *typg) +{ + if (G_LIKELY(typg != NULL)) { + g_free(typg); + } +} + +FbApiUser * +fb_api_user_dup(const FbApiUser *user, gboolean deep) +{ + FbApiUser *ret; + + if (user == NULL) { + return g_new0(FbApiUser, 1); + } + + ret = g_memdup2(user, sizeof *user); + + if (deep) { + ret->name = g_strdup(user->name); + ret->icon = g_strdup(user->icon); + ret->csum = g_strdup(user->csum); + } + + return ret; +} + +void +fb_api_user_reset(FbApiUser *user, gboolean deep) +{ + g_return_if_fail(user != NULL); + + if (deep) { + g_free(user->name); + g_free(user->icon); + g_free(user->csum); + } + + memset(user, 0, sizeof *user); +} + +void +fb_api_user_free(FbApiUser *user) +{ + if (G_LIKELY(user != NULL)) { + g_free(user->name); + g_free(user->icon); + g_free(user->csum); + g_free(user); + } +} diff --git a/pidgin/libpurple/protocols/facebook/api.h b/pidgin/libpurple/protocols/facebook/api.h new file mode 100644 index 00000000..63c9a1b9 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/api.h @@ -0,0 +1,1006 @@ +/* purple + * + * 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 + */ + +#ifndef _FACEBOOK_API_H_ +#define _FACEBOOK_API_H_ + +/** + * SECTION:api + * @section_id: facebook-api + * @short_description: api.h + * @title: Facebook API + * + * The API for interacting with the Facebook Messenger protocol. + */ + +#include "internal.h" + +#include + +#include "connection.h" + +#include "http.h" +#include "id.h" +#include "mqtt.h" + +#define FB_TYPE_API (fb_api_get_type()) +#define FB_API(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_API, FbApi)) +#define FB_API_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_API, FbApiClass)) +#define FB_IS_API(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_API)) +#define FB_IS_API_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_API)) +#define FB_API_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_API, FbApiClass)) + +/** + * FB_API_AHOST: + * + * The HTTP host for the Facebook API. + */ +#define FB_API_AHOST "https://api.facebook.com" + +/** + * FB_API_BHOST: + * + * The HTTP host for the Facebook BAPI. + */ +#define FB_API_BHOST "https://b-api.facebook.com" + +/** + * FB_API_GHOST: + * + * The HTTP host for the Facebook Graph API. + */ +#define FB_API_GHOST "https://graph.facebook.com" + +/** + * FB_API_WHOST: + * + * The HTTP host for the Facebook website. + */ +#define FB_API_WHOST "https://www.facebook.com" + +/** + * FB_API_FBRPC_PREFIX + * + * The fbrpc URL prefix used in links shared from the mobile app. + */ +#define FB_API_FBRPC_PREFIX "fbrpc://facebook/nativethirdparty" + +/** + * FB_API_KEY: + * + * The Facebook API key. + */ +#define FB_API_KEY "256002347743983" + +/** + * FB_API_SECRET: + * + * The Facebook API secret. + */ +#define FB_API_SECRET "374e60f8b9bb6b8cbb30f78030438895" + +/** + * FB_ORCA_AGENT + * + * The part of the user agent that looks like the official client, since the + * server started checking this. + */ + +#define FB_ORCA_AGENT "[FBAN/Orca-Android;FBAV/537.0.0.31.101;FBPN/com.facebook.orca;FBLC/en_US;FBBV/52182662]" + +/** + * FB_API_AGENT: + * + * The HTTP User-Agent header. + */ +#define FB_API_AGENT "Facebook plugin / Purple / " PACKAGE_VERSION " " FB_ORCA_AGENT + +/** + * FB_API_MQTT_AGENT + * + * The client information string sent in the MQTT CONNECT message + */ + +#define FB_API_MQTT_AGENT FB_API_AGENT + +/** + * FB_API_URL_ATTACH: + * + * The URL for attachment URL requests. + */ +#define FB_API_URL_ATTACH FB_API_AHOST "/method/messaging.getAttachment" +//#define FB_API_URL_ATTACH FB_API_AHOST "/method/messaging.attachmentRedirect" + +/** + * FB_API_URL_AUTH: + * + * The URL for authentication requests. + */ +#define FB_API_URL_AUTH FB_API_BHOST "/method/auth.login" + +/** + * FB_API_URL_GQL: + * + * The URL for GraphQL requests. + */ +#define FB_API_URL_GQL FB_API_GHOST "/graphql" + +/** + * FB_API_URL_MESSAGES: + * + * The URL for linking message threads. + */ +#define FB_API_URL_MESSAGES FB_API_WHOST "/messages" + +/** + * FB_API_URL_PARTS: + * + * The URL for participant management requests. + */ +#define FB_API_URL_PARTS FB_API_GHOST "/participants" + +/** + * FB_API_URL_THREADS: + * + * The URL for thread management requests. + */ +#define FB_API_URL_THREADS FB_API_GHOST "/me/group_threads" + +/** + * FB_API_URL_TOPIC: + * + * The URL for thread topic requests. + */ +#define FB_API_URL_TOPIC FB_API_AHOST "/method/messaging.setthreadname" + +/** + * FB_API_QUERY_CONTACT: + * + * The query hash for the `UsersQuery`. + * + * Key mapping: + * 0: user_fbids + * 1: include_full_user_info + * 2: profile_pic_large_size + * 3: profile_pic_medium_size + * 4: profile_pic_small_size + */ +#define FB_API_QUERY_CONTACT 10153915107411729 + +/** + * FB_API_QUERY_CONTACTS: + * + * The query hash for the `FetchContactsFullQuery`. + * + * Key mapping: + * 0: profile_types + * 1: limit + * 2: big_img_size + * 3: huge_img_size + * 4: small_img_size + */ +#define FB_API_QUERY_CONTACTS 10154444360806729 + +/** + * FB_API_QUERY_CONTACTS_AFTER: + * + * The query hash for the `FetchContactsFullWithAfterQuery`. + * + * Key mapping: + * 0: profile_types + * 1: after + * 2: limit + * 3: big_img_size + * 4: huge_img_size + * 5: small_img_size + */ +#define FB_API_QUERY_CONTACTS_AFTER 10154444360816729 + + +/** + * FB_API_QUERY_CONTACTS_DELTA: + * + * The query hash for the `FetchContactsDeltaQuery`. + * + * Key mapping: + * 0: after + * 1: profile_types + * 2: limit + * 3: big_img_size + * 4: huge_img_size + * 5: small_img_size + */ +#define FB_API_QUERY_CONTACTS_DELTA 10154444360801729 + +/** + * FB_API_QUERY_STICKER: + * + * The query hash for the `FetchStickersWithPreviewsQuery`. + * + * Key mapping: + * 0: sticker_ids + * 1: media_type + * 2: preview_size + * 3: scaling_factor + * 4: animated_media_type + */ +#define FB_API_QUERY_STICKER 10152877994321729 + +/** + * FB_API_QUERY_THREAD: + * + * The query hash for the `ThreadQuery`. + * + * Key mapping: + * 0: thread_ids + * 1: verification_type + * 2: hash_key + * 3: small_preview_size + * 4: large_preview_size + * 5: item_count + * 6: event_count + * 7: full_screen_height + * 8: full_screen_width + * 9: medium_preview_size + * 10: fetch_users_separately + * 11: include_message_info + * 12: msg_count + * 13: include_full_user_info + * 14: profile_pic_large_size + * 15: profile_pic_medium_size + * 16: profile_pic_small_size + */ +#define FB_API_QUERY_THREAD 10153919752036729 + +/** + * FB_API_QUERY_THREADS: + * + * The query hash for the `ThreadListQuery`. + * + * Key mapping: + * 0: folder_tag + * 1: thread_count + * 2: include_thread_info + * 3: verification_type + * 4: hash_key + * 5: small_preview_size + * 6: large_preview_size + * 7: item_count + * 8: event_count + * 9: full_screen_height + * 10: full_screen_width + * 11: medium_preview_size + * 12: fetch_users_separately + * 13: include_message_info + * 14: msg_count + * 15: UNKNOWN + * 16: profile_pic_large_size + * 17: profile_pic_medium_size + * 18: profile_pic_small_size + */ +#define FB_API_QUERY_THREADS 10153919752026729 + +/** + * FB_API_QUERY_SEQ_ID: + * + * A variant of ThreadListQuery with sequence ID + * + * TODO: parameters. + */ + +#define FB_API_QUERY_SEQ_ID 10155268192741729 + +/** + * FB_API_QUERY_XMA: + * + * The query hash for the `XMAQuery`. + * + * Key mapping: + * 0: xma_id + */ +#define FB_API_QUERY_XMA 10153919431161729 + +/** + * FB_API_CONTACTS_COUNT: + * + * The maximum amount of contacts to fetch in a single request. If this + * value is set too high, HTTP request will fail. This is due to the + * request data being too large. + */ +#define FB_API_CONTACTS_COUNT 500 + +/** + * FB_API_TCHK: + * @e: The expression. + * + * Checks the Thrift related expression to ensure that it evaluates to + * #TRUE. If the expression evaluates to #FALSE, a #GError is assigned + * to the local `error` variable, then returns with no value. + * + * This macro is meant to only be used for Thrift related expressions, + * where the calling function has a `void` return type. This macro also + * requires the existence of a predefined `error` variable, which is a + * pointer of a pointer to a #GError. + */ +#define FB_API_TCHK(e) \ + G_STMT_START { \ + if (G_UNLIKELY(!(e))) { \ + g_set_error(error, FB_API_ERROR, FB_API_ERROR_GENERAL, \ + "Failed to read thrift: %s:%d " \ + "%s: assertion '%s' failed", \ + __FILE__, __LINE__, G_STRFUNC, #e); \ + return; \ + } \ + } G_STMT_END + +/** + * FB_API_MSGID: + * @m: The time in milliseconds. + * @i: The random integer. + * + * Creates a 64-bit message identifier in the Facebook format. + * + * Returns: The message identifier. + */ +#define FB_API_MSGID(m, i) ((guint64) ( \ + (((guint32) i) & 0x3FFFFF) | \ + (((guint64) m) << 22) \ + )) + +/** + * FB_API_ERROR_EMIT: + * @a: The #FbApi. + * @e: The #FbApiError. + * @c: The code to execute. + * + * Emits a #GError on behalf of the #FbApi. + */ +#define FB_API_ERROR_EMIT(a, e, c) \ + G_STMT_START { \ + if (G_UNLIKELY((e) != NULL)) { \ + fb_api_error_emit(a, e); \ + {c;} \ + } \ + } G_STMT_END + +/** + * FB_API_ERROR: + * + * The #GQuark of the domain of API errors. + */ +#define FB_API_ERROR fb_api_error_quark() + +typedef struct _FbApi FbApi; +typedef struct _FbApiClass FbApiClass; +typedef struct _FbApiPrivate FbApiPrivate; +typedef struct _FbApiEvent FbApiEvent; +typedef struct _FbApiMessage FbApiMessage; +typedef struct _FbApiPresence FbApiPresence; +typedef struct _FbApiThread FbApiThread; +typedef struct _FbApiTyping FbApiTyping; +typedef struct _FbApiUser FbApiUser; + +/** + * FbApiError: + * @FB_API_ERROR_GENERAL: General failure. + * @FB_API_ERROR_AUTH: Authentication failure. + * @FB_API_ERROR_QUEUE: Queue failure. + * @FB_API_ERROR_NONFATAL: Other non-fatal errors. + * + * The error codes for the #FB_API_ERROR domain. + */ +typedef enum +{ + FB_API_ERROR_GENERAL, + FB_API_ERROR_AUTH, + FB_API_ERROR_QUEUE, + FB_API_ERROR_NONFATAL +} FbApiError; + +/** + * FbApiEventType: + * @FB_API_EVENT_TYPE_THREAD_TOPIC: The thread topic was changed. + * @FB_API_EVENT_TYPE_THREAD_USER_ADDED: A thread user was added. + * @FB_API_EVENT_TYPE_THREAD_USER_REMOVED: A thread user was removed. + * + * The #FbApiEvent types. + */ +typedef enum +{ + FB_API_EVENT_TYPE_THREAD_TOPIC, + FB_API_EVENT_TYPE_THREAD_USER_ADDED, + FB_API_EVENT_TYPE_THREAD_USER_REMOVED +} FbApiEventType; + +/** + * FbApiMessageFlags: + * @FB_API_MESSAGE_FLAG_DONE: The text has been processed. + * @FB_API_MESSAGE_FLAG_IMAGE: The text is a URL to an image. + * @FB_API_MESSAGE_FLAG_SELF: The text is from the #FbApi user. + * + * The #FbApiMessage flags. + */ +typedef enum +{ + FB_API_MESSAGE_FLAG_DONE = 1 << 0, + FB_API_MESSAGE_FLAG_IMAGE = 1 << 1, + FB_API_MESSAGE_FLAG_SELF = 1 << 2 +} FbApiMessageFlags; + +/** + * FbApi: + * + * Represents a Facebook Messenger connection. + */ +struct _FbApi +{ + /*< private >*/ + GObject parent; + FbApiPrivate *priv; +}; + +/** + * FbApiClass: + * + * The base class for all #FbApi's. + */ +struct _FbApiClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +/** + * FbApiEvent: + * @type: The #FbApiEventType. + * @uid: The user #FbId. + * @tid: The thread #FbId. + * @text: The event text. + * + * Represents a Facebook update event. + */ +struct _FbApiEvent +{ + FbApiEventType type; + FbId uid; + FbId tid; + gchar *text; +}; + +/** + * FbApiMessage: + * @flags: The #FbApiMessageFlags. + * @uid: The user #FbId. + * @tid: The thread #FbId. + * @tstamp: The timestamp in milliseconds (UTC). + * @text: The message text. + * + * Represents a Facebook user message. + */ +struct _FbApiMessage +{ + FbApiMessageFlags flags; + FbId uid; + FbId tid; + gint64 tstamp; + gchar *text; +}; + +/** + * FbApiPresence: + * @uid: The user #FbId. + * @active: #TRUE if the user is active, otherwise #FALSE. + * + * Represents a Facebook presence message. + */ +struct _FbApiPresence +{ + FbId uid; + gboolean active; +}; + +/** + * FbApiThread: + * @tid: The thread #FbId. + * @topic: The topic. + * @users: The #GSList of #FbApiUser's. + * + * Represents a Facebook message thread. + */ +struct _FbApiThread +{ + FbId tid; + gchar *topic; + GSList *users; +}; + +/** + * FbApiTyping: + * @uid: The user #FbId. + * @state: #TRUE if the user is typing, otherwise #FALSE. + * + * Represents a Facebook typing message. + */ +struct _FbApiTyping +{ + FbId uid; + gboolean state; +}; + +/** + * FbApiUser: + * @uid: The user #FbId. + * @name: The name of the user. + * @icon: The icon URL. + * @csum: The checksum of @icon. + * + * Represents a Facebook user. + */ +struct _FbApiUser +{ + FbId uid; + gchar *name; + gchar *icon; + gchar *csum; +}; + +/** + * fb_api_get_type: + * + * Returns: The #GType for an #FbApi. + */ +GType +fb_api_get_type(void); + +/** + * fb_api_error_quark: + * + * Gets the #GQuark of the domain of API errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_api_error_quark(void); + +/** + * fb_api_new: + * @gc: The #PurpleConnection. + * + * Creates a new #FbApi. The returned #FbApi should be freed with + * #g_object_unref() when no longer needed. + * + * Returns: The new #FbApi. + */ +FbApi * +fb_api_new(PurpleConnection *gc); + +/** + * fb_api_rehash: + * @api: The #FbApi. + * + * Rehashes and updates internal data of the #FbApi. This should be + * called whenever properties are modified. + */ +void +fb_api_rehash(FbApi *api); + +/** + * fb_api_is_invisible: + * @api: The #FbApi. + * + * Determines if the user of the #FbApi is invisible. + * + * Returns: #TRUE if the #FbApi user is invisible, otherwise #FALSE. + */ +gboolean +fb_api_is_invisible(FbApi *api); + +/** + * fb_api_error: + * @api: The #FbApi. + * @error: The #FbApiError. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Emits an #FbApiError. + */ +void +fb_api_error(FbApi *api, FbApiError error, const gchar *format, ...) + G_GNUC_PRINTF(3, 4); + +/** + * fb_api_error_emit: + * @api: The #FbApi. + * @error: The #GError. + * + * Emits a #GError on an #FbApiError. + */ +void +fb_api_error_emit(FbApi *api, GError *error); + +/** + * fb_api_auth: + * @api: The #FbApi. + * @user: The Facebook user name, email, or phone number. + * @pass: The Facebook password. + * + * Sends an authentication request to Facebook. This will obtain + * session information, which is required for all other requests. + */ +void +fb_api_auth(FbApi *api, const gchar *user, const gchar *pass); + +/** + * fb_api_contact: + * @api: The #FbApi. + * @uid: The user #FbId. + * + * Sends a contact request. This will obtain the general information of + * a single contact. + */ +void +fb_api_contact(FbApi *api, FbId uid); + +/** + * fb_api_contacts: + * @api: The #FbApi. + * + * Sends a contacts request. This will obtain a full list of detailed + * contact information about the friends of the #FbApi user. + */ +void +fb_api_contacts(FbApi *api); + +/** + * fb_api_connect: + * @api: The #FbApi. + * @invisible: #TRUE to make the user invisible, otherwise #FALSE. + * + * Initializes and establishes the underlying MQTT connection. + */ +void +fb_api_connect(FbApi *api, gboolean invisible); + +/** + * fb_api_disconnect: + * @api: The #FbApi. + * + * Closes the underlying MQTT connection. + */ +void +fb_api_disconnect(FbApi *api); + +/** + * fb_api_message: + * @api: The #FbApi. + * @id: The user or thread #FbId. + * @thread: #TRUE if @id is a thread, otherwise #FALSE. + * @text: The message text. + * + * Sends a message as the user of the #FbApi to a user or a thread. + */ +void +fb_api_message(FbApi *api, FbId id, gboolean thread, const gchar *text); + +/** + * fb_api_publish: + * @api: The #FbApi. + * @topic: The topic. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Publishes an MQTT message. + */ +void +fb_api_publish(FbApi *api, const gchar *topic, const gchar *format, ...) + G_GNUC_PRINTF(3, 4); + +/** + * fb_api_read: + * @api: The #FbApi. + * @id: The user or thread #FbId. + * @thread: #TRUE if @id is a thread, otherwise #FALSE. + * + * Marks a message thread as read. + */ +void +fb_api_read(FbApi *api, FbId id, gboolean thread); + +/** + * fb_api_unread: + * @api: The #FbApi. + * + * Sends an unread message request. + */ +void +fb_api_unread(FbApi *api); + +/** + * fb_api_thread: + * @api: The #FbApi. + * @tid: The thread #FbId. + * + * Sends a thread request. This will obtain the general information of + * a single thread. + */ +void +fb_api_thread(FbApi *api, FbId tid); + +/** + * fb_api_thread_create: + * @api: The #FbApi. + * @uids: The #GSList of #FbId's. + * + * Sends a thread creation request. In order to create a thread, there + * must be at least two other users in @uids. + */ +void +fb_api_thread_create(FbApi *api, GSList *uids); + +/** + * fb_api_thread_invite: + * @api: The #FbApi. + * @tid: The thread #FbId. + * @uid: The user #FbId. + * + * Sends a thread user invitation request. + */ +void +fb_api_thread_invite(FbApi *api, FbId tid, FbId uid); + +/** + * fb_api_thread_remove: + * @api: The #FbApi. + * @tid: The thread #FbId. + * @uid: The user #FbId. + * + * Sends a thread user removal request. + */ +void +fb_api_thread_remove(FbApi *api, FbId tid, FbId uid); + +/** + * fb_api_thread_topic: + * @api: The #FbApi. + * @tid: The thread #FbId. + * @topic: The topic. + * + * Sends a thread topic change request. + */ +void +fb_api_thread_topic(FbApi *api, FbId tid, const gchar *topic); + +/** + * fb_api_threads: + * @api: The #FbApi. + * + * Sends a threads request. This will obtain a full list of detailed + * thread information about the threads of the #FbApi user. + */ +void +fb_api_threads(FbApi *api); + +/** + * fb_api_typing: + * @api: The #FbApi. + * @uid: The user #FbId. + * @state: #TRUE if the #FbApi user is typing, otherwise #FALSE. + * + * Sends a typing state message for the user of the #FbApi. + */ +void +fb_api_typing(FbApi *api, FbId uid, gboolean state); + +/** + * fb_api_event_dup: + * @event: The #FbApiEvent or #NULL. + * @deep: #TRUE to duplicate allocated data, otherwise #FALSE. + * + * Duplicates an #FbApiEvent. If @event is #NULL, a new zero filled + * #FbApiEvent is returned. The returned #FbApiEvent should be freed + * with #fb_api_event_free() when no longer needed. + * + * Returns: The new #FbApiEvent. + */ +FbApiEvent * +fb_api_event_dup(const FbApiEvent *event, gboolean deep); + +/** + * fb_api_event_reset: + * @event: The #FbApiEvent. + * @deep: #TRUE to free allocated data, otherwise #FALSE. + * + * Resets an #FbApiEvent. + */ +void +fb_api_event_reset(FbApiEvent *event, gboolean deep); + +/** + * fb_api_event_free: + * @event: The #FbApiEvent. + * + * Frees all memory used by the #FbApiEvent. + */ +void +fb_api_event_free(FbApiEvent *event); + +/** + * fb_api_message_dup: + * @msg: The #FbApiMessage or #NULL. + * @deep: #TRUE to duplicate allocated data, otherwise #FALSE. + * + * Duplicates an #FbApiMessage. If @msg is #NULL, a new zero filled + * #FbApiMessage is returned. The returned #FbApiMessage should be + * freed with #fb_api_message_free() when no longer needed. + * + * Returns: The new #FbApiMessage. + */ +FbApiMessage * +fb_api_message_dup(const FbApiMessage *msg, gboolean deep); + +/** + * fb_api_message_reset: + * @msg: The #FbApiMessage. + * @deep: #TRUE to free allocated data, otherwise #FALSE. + * + * Resets an #FbApiMessage. + */ +void +fb_api_message_reset(FbApiMessage *msg, gboolean deep); + +/** + * fb_api_message_free: + * @msg: The #FbApiMessage. + * + * Frees all memory used by the #FbApiMessage. + */ +void +fb_api_message_free(FbApiMessage *msg); + +/** + * fb_api_presence_dup: + * @pres: The #FbApiPresence or #NULL. + * + * Duplicates an #FbApiPresence. If @pres is #NULL, a new zero filled + * #FbApiPresence is returned. The returned #FbApiPresence should be + * freed with #fb_api_presence_free() when no longer needed. + * + * Returns: The new #FbApiPresence. + */ +FbApiPresence * +fb_api_presence_dup(const FbApiPresence *pres); + +/** + * fb_api_presence_reset: + * @pres: The #FbApiPresence. + * + * Resets an #FbApiPresence. + */ +void +fb_api_presence_reset(FbApiPresence *pres); + +/** + * fb_api_presence_free: + * @pres: The #FbApiPresence. + * + * Frees all memory used by the #FbApiPresence. + */ +void +fb_api_presence_free(FbApiPresence *pres); + +/** + * fb_api_thread_dup: + * @thrd: The #FbApiThread or #NULL. + * @deep: #TRUE to duplicate allocated data, otherwise #FALSE. + * + * Duplicates an #FbApiThread. If @thrd is #NULL, a new zero filled + * #FbApiThread is returned. The returned #FbApiThread should be freed + * with #fb_api_thread_free() when no longer needed. + * + * Returns: The new #FbApiThread. + */ +FbApiThread * +fb_api_thread_dup(const FbApiThread *thrd, gboolean deep); + +/** + * fb_api_thread_reset: + * @thrd: The #FbApiThread. + * @deep: #TRUE to free allocated data, otherwise #FALSE. + * + * Resets an #FbApiThread. + */ +void +fb_api_thread_reset(FbApiThread *thrd, gboolean deep); + +/** + * fb_api_thread_free: + * @thrd: The #FbApiThread. + * + * Frees all memory used by the #FbApiThread. + */ +void +fb_api_thread_free(FbApiThread *thrd); + +/** + * fb_api_typing_dup: + * @typg: The #FbApiTyping or #NULL. + * + * Duplicates an #FbApiTyping. If @typg is #NULL, a new zero filled + * #FbApiTyping is returned. The returned #FbApiTyping should be freed + * with #fb_api_typing_free() when no longer needed. + * + * Returns: The new #FbApiTyping. + */ +FbApiTyping * +fb_api_typing_dup(const FbApiTyping *typg); + +/** + * fb_api_typing_reset: + * @typg: The #FbApiTyping. + * + * Resets an #FbApiTyping. + */ +void +fb_api_typing_reset(FbApiTyping *typg); + +/** + * fb_api_typing_free: + * @typg: The #FbApiTyping. + * + * Frees all memory used by the #FbApiTyping. + */ +void +fb_api_typing_free(FbApiTyping *typg); + +/** + * fb_api_user_dup: + * @user: The #FbApiUser or #NULL. + * @deep: #TRUE to duplicate allocated data, otherwise #FALSE. + * + * Duplicates an #FbApiUser. If @user is #NULL, a new zero filled + * #FbApiUser is returned. The returned #FbApiUser should be freed with + * #fb_api_user_free() when no longer needed. + * + * Returns: The new #FbApiUser. + */ +FbApiUser * +fb_api_user_dup(const FbApiUser *user, gboolean deep); + +/** + * fb_api_user_reset: + * @user: The #FbApiUser. + * @deep: #TRUE to free allocated data, otherwise #FALSE. + * + * Resets an #FbApiUser. + */ +void +fb_api_user_reset(FbApiUser *user, gboolean deep); + +/** + * fb_api_user_free: + * @user: The #FbApiUser. + * + * Frees all memory used by the #FbApiUser. + */ +void +fb_api_user_free(FbApiUser *user); + +#endif /* _FACEBOOK_API_H_ */ diff --git a/pidgin/libpurple/protocols/facebook/data.c b/pidgin/libpurple/protocols/facebook/data.c new file mode 100644 index 00000000..6ade3bf5 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/data.c @@ -0,0 +1,608 @@ +/* purple + * + * 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 + +#include "account.h" +#include "glibcompat.h" + +#include "api.h" +#include "data.h" + +struct _FbDataPrivate +{ + FbApi *api; + FbHttpConns *cons; + PurpleConnection *gc; + PurpleRoomlist *roomlist; + GQueue *msgs; + GHashTable *imgs; + GHashTable *unread; + GHashTable *evs; +}; + +struct _FbDataImagePrivate +{ + FbData *fata; + gchar *url; + FbDataImageFunc func; + gpointer data; + GDestroyNotify dunc; + + gboolean active; + const guint8 *image; + gsize size; +}; + +static const gchar *fb_props_strs[] = { + "cid", + "did", + "stoken", + "token" +}; + +G_DEFINE_TYPE_WITH_CODE(FbData, fb_data, G_TYPE_OBJECT, G_ADD_PRIVATE(FbData)); +G_DEFINE_TYPE_WITH_CODE(FbDataImage, fb_data_image, G_TYPE_OBJECT, G_ADD_PRIVATE(FbDataImage)); + +static void +fb_data_dispose(GObject *obj) +{ + FbDataPrivate *priv = FB_DATA(obj)->priv; + GHashTableIter iter; + gpointer ptr; + + fb_http_conns_cancel_all(priv->cons); + g_hash_table_iter_init(&iter, priv->evs); + + while (g_hash_table_iter_next(&iter, NULL, &ptr)) { + g_source_remove(GPOINTER_TO_UINT(ptr)); + } + + if (G_LIKELY(priv->api != NULL)) { + g_object_unref(priv->api); + } + + fb_http_conns_free(priv->cons); + g_queue_free_full(priv->msgs, (GDestroyNotify) fb_api_message_free); + + g_hash_table_destroy(priv->imgs); + g_hash_table_destroy(priv->unread); + g_hash_table_destroy(priv->evs); +} + +static void +fb_data_class_init(FbDataClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); + + gklass->dispose = fb_data_dispose; + g_type_class_add_private(klass, sizeof (FbDataPrivate)); +} + +static void +fb_data_init(FbData *fata) +{ + FbDataPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(fata, FB_TYPE_DATA, FbDataPrivate); + fata->priv = priv; + + priv->cons = fb_http_conns_new(); + priv->msgs = g_queue_new(); + + priv->imgs = g_hash_table_new_full(g_direct_hash, g_direct_equal, + g_object_unref, NULL); + priv->unread = g_hash_table_new_full(fb_id_hash, fb_id_equal, + g_free, NULL); + priv->evs = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); +} + +static void +fb_data_image_dispose(GObject *obj) +{ + FbDataImage *img = FB_DATA_IMAGE(obj); + FbDataImagePrivate *priv = img->priv; + FbData *fata = priv->fata; + + if ((priv->dunc != NULL) && (priv->data != NULL)) { + priv->dunc(priv->data); + } + + g_free(priv->url); + g_hash_table_steal(fata->priv->imgs, img); +} + +static void +fb_data_image_class_init(FbDataImageClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); + + gklass->dispose = fb_data_image_dispose; + g_type_class_add_private(klass, sizeof (FbDataImagePrivate)); +} + +static void +fb_data_image_init(FbDataImage *img) +{ + FbDataImagePrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(img, FB_TYPE_DATA_IMAGE, + FbDataImagePrivate); + img->priv = priv; +} + +FbData * +fb_data_new(PurpleConnection *gc) +{ + FbData *fata; + FbDataPrivate *priv; + + fata = g_object_new(FB_TYPE_DATA, NULL); + priv = fata->priv; + + priv->api = fb_api_new(gc); + priv->gc = gc; + + return fata; +} + +gboolean +fb_data_load(FbData *fata) +{ + const gchar *str; + FbDataPrivate *priv; + FbId id; + gboolean ret = TRUE; + guint i; + guint64 uint; + GValue val = G_VALUE_INIT; + PurpleAccount *acct; + + g_return_val_if_fail(FB_IS_DATA(fata), FALSE); + priv = fata->priv; + acct = purple_connection_get_account(priv->gc); + + for (i = 0; i < G_N_ELEMENTS(fb_props_strs); i++) { + str = purple_account_get_string(acct, fb_props_strs[i], NULL); + + if (str == NULL) { + ret = FALSE; + } + + g_value_init(&val, G_TYPE_STRING); + g_value_set_string(&val, str); + g_object_set_property(G_OBJECT(priv->api), fb_props_strs[i], + &val); + g_value_unset(&val); + } + + str = purple_account_get_string(acct, "mid", NULL); + + if (str != NULL) { + uint = g_ascii_strtoull(str, NULL, 10); + g_value_init(&val, G_TYPE_UINT64); + g_value_set_uint64(&val, uint); + g_object_set_property(G_OBJECT(priv->api), "mid", &val); + g_value_unset(&val); + } else { + ret = FALSE; + } + + str = purple_account_get_string(acct, "uid", NULL); + + if (str != NULL) { + id = FB_ID_FROM_STR(str); + g_value_init(&val, FB_TYPE_ID); + g_value_set_int64(&val, id); + g_object_set_property(G_OBJECT(priv->api), "uid", &val); + g_value_unset(&val); + } else { + ret = FALSE; + } + + fb_api_rehash(priv->api); + return ret; +} + +void +fb_data_save(FbData *fata) +{ + const gchar *str; + FbDataPrivate *priv; + gchar *dup; + guint i; + guint64 uint; + GValue val = G_VALUE_INIT; + PurpleAccount *acct; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + acct = purple_connection_get_account(priv->gc); + + for (i = 0; i < G_N_ELEMENTS(fb_props_strs); i++) { + g_value_init(&val, G_TYPE_STRING); + g_object_get_property(G_OBJECT(priv->api), fb_props_strs[i], + &val); + str = g_value_get_string(&val); + + if (purple_strequal(fb_props_strs[i], "token") && !purple_account_get_remember_password(acct)) { + str = ""; + } + purple_account_set_string(acct, fb_props_strs[i], str); + g_value_unset(&val); + } + + g_value_init(&val, G_TYPE_UINT64); + g_object_get_property(G_OBJECT(priv->api), "mid", &val); + uint = g_value_get_uint64(&val); + g_value_unset(&val); + + dup = g_strdup_printf("%" G_GINT64_FORMAT, uint); + purple_account_set_string(acct, "mid", dup); + g_free(dup); + + g_value_init(&val, G_TYPE_INT64); + g_object_get_property(G_OBJECT(priv->api), "uid", &val); + uint = g_value_get_int64(&val); + g_value_unset(&val); + + dup = g_strdup_printf("%" FB_ID_FORMAT, uint); + purple_account_set_string(acct, "uid", dup); + g_free(dup); +} + +void +fb_data_add_timeout(FbData *fata, const gchar *name, guint interval, + GSourceFunc func, gpointer data) +{ + FbDataPrivate *priv; + gchar *key; + guint id; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + fb_data_clear_timeout(fata, name, TRUE); + + key = g_strdup(name); + id = g_timeout_add(interval, func, data); + g_hash_table_replace(priv->evs, key, GUINT_TO_POINTER(id)); +} + +void +fb_data_clear_timeout(FbData *fata, const gchar *name, gboolean remove) +{ + FbDataPrivate *priv; + gpointer ptr; + guint id; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + ptr = g_hash_table_lookup(priv->evs, name); + id = GPOINTER_TO_UINT(ptr); + + if ((id > 0) && remove) { + g_source_remove(id); + } + + g_hash_table_remove(priv->evs, name); +} + +FbApi * +fb_data_get_api(FbData *fata) +{ + FbDataPrivate *priv; + + g_return_val_if_fail(FB_IS_DATA(fata), NULL); + priv = fata->priv; + + return priv->api; +} + +PurpleConnection * +fb_data_get_connection(FbData *fata) +{ + FbDataPrivate *priv; + + g_return_val_if_fail(FB_IS_DATA(fata), NULL); + priv = fata->priv; + + return priv->gc; +} + +PurpleRoomlist * +fb_data_get_roomlist(FbData *fata) +{ + FbDataPrivate *priv; + + g_return_val_if_fail(FB_IS_DATA(fata), NULL); + priv = fata->priv; + + return priv->roomlist; +} + +gboolean +fb_data_get_unread(FbData *fata, FbId id) +{ + FbDataPrivate *priv; + gpointer *ptr; + + g_return_val_if_fail(FB_IS_DATA(fata), FALSE); + g_return_val_if_fail(id != 0, FALSE); + priv = fata->priv; + + ptr = g_hash_table_lookup(priv->unread, &id); + return GPOINTER_TO_INT(ptr); +} + +void +fb_data_set_roomlist(FbData *fata, PurpleRoomlist *list) +{ + FbDataPrivate *priv; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + priv->roomlist = list; +} + +void +fb_data_set_unread(FbData *fata, FbId id, gboolean unread) +{ + FbDataPrivate *priv; + gpointer key; + + g_return_if_fail(FB_IS_DATA(fata)); + g_return_if_fail(id != 0); + priv = fata->priv; + + if (!unread) { + g_hash_table_remove(priv->unread, &id); + return; + } + + key = g_memdup2(&id, sizeof id); + g_hash_table_replace(priv->unread, key, GINT_TO_POINTER(unread)); +} + +void +fb_data_add_message(FbData *fata, FbApiMessage *msg) +{ + FbDataPrivate *priv; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + g_queue_push_tail(priv->msgs, msg); +} + +void +fb_data_remove_message(FbData *fata, FbApiMessage *msg) +{ + FbDataPrivate *priv; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + g_queue_remove(priv->msgs, msg); +} + +GSList * +fb_data_take_messages(FbData *fata, FbId uid) +{ + FbApiMessage *msg; + FbDataPrivate *priv; + GList *l; + GList *prev; + GSList *msgs = NULL; + + g_return_val_if_fail(FB_IS_DATA(fata), NULL); + priv = fata->priv; + l = priv->msgs->tail; + + while (l != NULL) { + msg = l->data; + prev = l->prev; + + if (msg->uid == uid) { + msgs = g_slist_prepend(msgs, msg); + g_queue_delete_link(priv->msgs, l); + } + + l = prev; + } + + return msgs; +} + +FbDataImage * +fb_data_image_add(FbData *fata, const gchar *url, FbDataImageFunc func, + gpointer data, GDestroyNotify dunc) +{ + FbDataImage *img; + FbDataImagePrivate *priv; + + g_return_val_if_fail(FB_IS_DATA(fata), NULL); + g_return_val_if_fail(url != NULL, NULL); + g_return_val_if_fail(func != NULL, NULL); + + img = g_object_new(FB_TYPE_DATA_IMAGE, NULL); + priv = img->priv; + + priv->fata = fata; + priv->url = g_strdup(url); + priv->func = func; + priv->data = data; + priv->dunc = dunc; + + g_hash_table_insert(fata->priv->imgs, img, img); + return img; +} + +gboolean +fb_data_image_get_active(FbDataImage *img) +{ + FbDataImagePrivate *priv; + + g_return_val_if_fail(FB_IS_DATA_IMAGE(img), FALSE); + priv = img->priv; + + return priv->active; +} + +gpointer +fb_data_image_get_data(FbDataImage *img) +{ + FbDataImagePrivate *priv; + + g_return_val_if_fail(FB_IS_DATA_IMAGE(img), NULL); + priv = img->priv; + + return priv->data; +} + +FbData * +fb_data_image_get_fata(FbDataImage *img) +{ + FbDataImagePrivate *priv; + + g_return_val_if_fail(FB_IS_DATA_IMAGE(img), NULL); + priv = img->priv; + + return priv->fata; +} + +const guint8 * +fb_data_image_get_image(FbDataImage *img, gsize *size) +{ + FbDataImagePrivate *priv; + + g_return_val_if_fail(FB_IS_DATA_IMAGE(img), NULL); + priv = img->priv; + + if (size != NULL) { + *size = priv->size; + } + + return priv->image; +} + +guint8 * +fb_data_image_dup_image(FbDataImage *img, gsize *size) +{ + FbDataImagePrivate *priv; + + g_return_val_if_fail(FB_IS_DATA_IMAGE(img), NULL); + priv = img->priv; + + if (size != NULL) { + *size = priv->size; + } + + if (priv->size < 1) { + return NULL; + } + + return g_memdup2(priv->image, priv->size); +} + +const gchar * +fb_data_image_get_url(FbDataImage *img) +{ + FbDataImagePrivate *priv; + + g_return_val_if_fail(FB_IS_DATA_IMAGE(img), NULL); + priv = img->priv; + + return priv->url; +} + +static void +fb_data_image_cb(PurpleHttpConnection *con, PurpleHttpResponse *res, + gpointer data) +{ + FbDataImage *img = data; + FbDataImagePrivate *priv = img->priv; + FbDataPrivate *driv = priv->fata->priv; + GError *err = NULL; + + if (fb_http_conns_is_canceled(driv->cons)) { + return; + } + + fb_http_conns_remove(driv->cons, con); + fb_http_error_chk(res, &err); + + priv->image = (guint8 *) purple_http_response_get_data(res, &priv->size); + priv->func(img, err); + + if (G_LIKELY(err == NULL)) { + fb_data_image_queue(priv->fata); + } else { + g_error_free(err); + } + + g_object_unref(img); +} + +void +fb_data_image_queue(FbData *fata) +{ + const gchar *url; + FbDataImage *img; + FbDataPrivate *priv; + GHashTableIter iter; + guint active = 0; + PurpleHttpConnection *con; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + g_hash_table_iter_init(&iter, priv->imgs); + + while (g_hash_table_iter_next(&iter, (gpointer *) &img, NULL)) { + if (fb_data_image_get_active(img)) { + active++; + } + } + + if (active >= FB_DATA_ICON_MAX) { + return; + } + + g_hash_table_iter_init(&iter, priv->imgs); + + while (g_hash_table_iter_next(&iter, (gpointer *) &img, NULL)) { + if (fb_data_image_get_active(img)) { + continue; + } + + img->priv->active = TRUE; + url = fb_data_image_get_url(img); + con = purple_http_get(priv->gc, fb_data_image_cb, img, url); + fb_http_conns_add(priv->cons, con); + + if (++active >= FB_DATA_ICON_MAX) { + break; + } + } +} diff --git a/pidgin/libpurple/protocols/facebook/data.h b/pidgin/libpurple/protocols/facebook/data.h new file mode 100644 index 00000000..dbcac479 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/data.h @@ -0,0 +1,398 @@ +/* purple + * + * 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 + */ + +#ifndef _FACEBOOK_DATA_H_ +#define _FACEBOOK_DATA_H_ + +/** + * SECTION:data + * @section_id: facebook-data + * @short_description: data.h + * @title: Connection Data + * + * The Connection Data. + */ + +#include + +#include "connection.h" +#include "roomlist.h" + +#include "api.h" +#include "http.h" +#include "id.h" + +#define FB_TYPE_DATA (fb_data_get_type()) +#define FB_DATA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_DATA, FbData)) +#define FB_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_DATA, FbDataClass)) +#define FB_IS_DATA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_DATA)) +#define FB_IS_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_DATA)) +#define FB_DATA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_DATA, FbDataClass)) + +#define FB_TYPE_DATA_IMAGE (fb_data_image_get_type()) +#define FB_DATA_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_DATA_IMAGE, FbDataImage)) +#define FB_DATA_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_DATA_IMAGE, FbDataImageClass)) +#define FB_IS_DATA_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_DATA_IMAGE)) +#define FB_IS_DATA_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_DATA_IMAGE)) +#define FB_DATA_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_DATA_IMAGE, FbDataImageClass)) + +/** + * FB_DATA_ICON_MAX: + * + * The maximum of number of concurrent icon fetches. + */ +#define FB_DATA_ICON_MAX 4 + +typedef struct _FbData FbData; +typedef struct _FbDataClass FbDataClass; +typedef struct _FbDataPrivate FbDataPrivate; +typedef struct _FbDataImage FbDataImage; +typedef struct _FbDataImageClass FbDataImageClass; +typedef struct _FbDataImagePrivate FbDataImagePrivate; + +/** + * FbDataImageFunc: + * @img: The #FbDataImage. + * @error: The #GError or #NULL. + * + * The callback for a fetched #FbDataImage. + */ +typedef void (*FbDataImageFunc) (FbDataImage *img, GError *error); + +/** + * FbData: + * + * Represents the connection data used by #FacebookProtocol. + */ +struct _FbData +{ + /*< private >*/ + GObject parent; + FbDataPrivate *priv; +}; + +/** + * FbDataClass: + * + * The base class for all #FbData's. + */ +struct _FbDataClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +/** + * FbDataImage: + * + * Represents the data used for fetching images. + */ +struct _FbDataImage +{ + /*< private >*/ + GObject parent; + FbDataImagePrivate *priv; +}; + +/** + * FbDataImageClass: + * + * The base class for all #FbDataImage's. + */ +struct _FbDataImageClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +/** + * fb_data_get_type: + * + * Returns: The #GType for an #FbData. + */ +GType +fb_data_get_type(void); + +/** + * fb_data_image_get_type: + * + * Returns: The #GType for an #FbDataImage. + */ +GType +fb_data_image_get_type(void); + +/** + * fb_data_new: + * @gc: The #PurpleConnection. + * + * Creates a new #FbData. The returned #FbData should be freed with + * #g_object_unref() when no longer needed. + * + * Returns: The new #FbData. + */ +FbData * +fb_data_new(PurpleConnection *gc); + +/** + * fb_data_load: + * @fata: The #FbData. + * + * Loads the internal data from the underlying #PurpleAccount. + * + * Return: #TRUE if all of the data was loaded, otherwise #FALSE. + */ +gboolean +fb_data_load(FbData *fata); + +/** + * fb_data_save: + * @fata: The #FbData. + * + * Saves the internal data to the underlying #PurpleAccount. + */ +void +fb_data_save(FbData *fata); + +/** + * fb_data_add_timeout: + * @fata: The #FbData. + * @name: The name of the timeout. + * @interval: The time, in milliseconds, between calls to @func. + * @func: The #GSourceFunc. + * @data: The data passed to @func. + * + * Adds a new callback timer. The callback is called repeatedly on the + * basis of @interval, until @func returns #FALSE. The timeout should + * be cleared with #fb_data_clear_timeout() when no longer needed. + */ +void +fb_data_add_timeout(FbData *fata, const gchar *name, guint interval, + GSourceFunc func, gpointer data); + +/** + * fb_data_clear_timeout: + * @fata: The #FbData. + * @name: The name of the timeout. + * @remove: #TRUE to remove from the event loop, otherwise #FALSE. + * + * Clears and removes a callback timer. The only time @remove should be + * #FALSE, is when being called from a #GSourceFunc, which is returning + * #FALSE. + */ +void +fb_data_clear_timeout(FbData *fata, const gchar *name, gboolean remove); + +/** + * fb_data_get_api: + * @fata: The #FbData. + * + * Gets the #FbApi from the #FbData. + * + * Return: The #FbApi. + */ +FbApi * +fb_data_get_api(FbData *fata); + +/** + * fb_data_get_connection: + * @fata: The #FbData. + * + * Gets the #PurpleConnection from the #FbData. + * + * Return: The #PurpleConnection. + */ +PurpleConnection * +fb_data_get_connection(FbData *fata); + +/** + * fb_data_get_roomlist: + * @fata: The #FbData. + * + * Gets the #PurpleRoomlist from the #FbData. + * + * Return: The #PurpleRoomlist. + */ +PurpleRoomlist * +fb_data_get_roomlist(FbData *fata); + +/** + * fb_data_get_unread: + * @fata: The #FbData. + * @id: The #FbId. + * + * Gets the unread state of an #FbId. + * + * Return: #TRUE if the #FbId is unread, otherwise #FALSE. + */ +gboolean +fb_data_get_unread(FbData *fata, FbId id); + +/** + * fb_data_set_roomlist: + * @fata: The #FbData. + * @list: The #PurpleRoomlist. + * + * Sets the #PurpleRoomlist to the #FbData. + */ +void +fb_data_set_roomlist(FbData *fata, PurpleRoomlist *list); + +/** + * fb_data_set_unread: + * @fata: The #FbData. + * @id: The #FbId. + * @unread: #TRUE if the #FbId is unread, otherwise #FALSE. + * + * Sets the unread state of an #FbId to the #FbData. + */ +void +fb_data_set_unread(FbData *fata, FbId id, gboolean unread); + +/** + * fb_data_add_message: + * @fata: The #FbData. + * @msg: The #FbApiMessage. + * + * Adds an #FbApiMessage to the #FbData. + */ +void +fb_data_add_message(FbData *fata, FbApiMessage *msg); + +/** + * fb_data_remove_message: + * @fata: The #FbData. + * @msg: The #FbApiMessage. + * + * Removes an #FbApiMessage from the #FbData. + */ +void +fb_data_remove_message(FbData *fata, FbApiMessage *msg); + +/** + * fb_data_take_messages: + * @fata: The #FbData. + * @uid: The user #FbId. + * + * Gets a #GSList of messages by the user #FbId from the #FbData. The + * #FbApiMessage's are removed from the #FbData. The returned #GSList + * and its #FbApiMessage's should be freed with #fb_api_message_free() + * and #g_slist_free_full() when no longer needed. + */ +GSList * +fb_data_take_messages(FbData *fata, FbId uid); + +/** + * fb_data_image_add: + * @fata: The #FbData. + * @url: The image URL. + * @func: The #FbDataImageFunc. + * @data: The user-defined data. + * @dunc: The #GDestroyNotify for @data or #NULL. + * + * Adds a new #FbDataImage to the #FbData. This is used to fetch images + * from HTTP sources. After calling this, #fb_data_image_queue() should + * be called to queue the fetching process. + * + * Return: The #FbDataImage. + */ +FbDataImage * +fb_data_image_add(FbData *fata, const gchar *url, FbDataImageFunc func, + gpointer data, GDestroyNotify dunc); + +/** + * fb_data_image_get_active: + * @img: The #FbDataImage. + * + * Gets the active fetching state from the #FbDataImage. + * + * Returns: #TRUE if the image is being fetched, otherwise #FALSE. + */ +gboolean +fb_data_image_get_active(FbDataImage *img); + +/** + * fb_data_image_get_data: + * @img: The #FbDataImage. + * + * Gets the user-defined data from the #FbDataImage. + * + * Returns: The user-defined data. + */ +gpointer +fb_data_image_get_data(FbDataImage *img); + +/** + * fb_data_image_get_fata: + * @img: The #FbDataImage. + * + * Gets the #FbData from the #FbDataImage. + * + * Returns: The #FbData. + */ +FbData * +fb_data_image_get_fata(FbDataImage *img); + +/** + * fb_data_image_get_image: + * @img: The #FbDataImage. + * @size: The return location for the image size or #NULL. + * + * Gets the image data from the #FbDataImage. + * + * Returns: The image data. + */ +const guint8 * +fb_data_image_get_image(FbDataImage *img, gsize *size); + +/** + * fb_data_image_dup_image: + * @img: The #FbDataImage. + * @size: The return location for the image size or #NULL. + * + * Gets the duplicated image data from the #FbDataImage. The returned + * data should be freed with #g_free() when no longer needed. + * + * Returns: The duplicated image data. + */ +guint8 * +fb_data_image_dup_image(FbDataImage *img, gsize *size); + +/** + * fb_data_image_get_url: + * @img: The #FbDataImage. + * + * Gets the image URL from the #FbDataImage. + * + * Returns: The image URL. + */ +const gchar * +fb_data_image_get_url(FbDataImage *img); + +/** + * fb_data_image_queue: + * @fata: The #FbData. + * + * Queues the next #FbDataImage fetches. + */ +void +fb_data_image_queue(FbData *fata); + +#endif /* _FACEBOOK_DATA_H_ */ diff --git a/pidgin/libpurple/protocols/facebook/facebook.c b/pidgin/libpurple/protocols/facebook/facebook.c new file mode 100644 index 00000000..01f3bf81 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/facebook.c @@ -0,0 +1,1685 @@ +/* purple + * + * 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 "accountopt.h" +#include "blistnode.h" +#include "buddy.h" +#include "buddyicon.h" +#include "buddylist.h" +#include "cmds.h" +#include "connection.h" +#include "conversation.h" +#include "conversations.h" +#include "conversationtypes.h" +#include "glibcompat.h" +#include "image.h" +#include "image-store.h" +#include "message.h" +#include "notify.h" +#include "plugins.h" +#include "presence.h" +#include "protocol.h" +#include "protocols.h" +#include "request.h" +#include "roomlist.h" +#include "server.h" +#include "signals.h" +#include "sslconn.h" +#include "status.h" +#include "util.h" +#include "version.h" + +#include "api.h" +#include "data.h" +#include "facebook.h" +#include "http.h" +#include "util.h" + +static GSList *fb_cmds = NULL; +static PurpleProtocol *fb_protocol = NULL; + +static void +fb_cb_api_messages(FbApi *api, GSList *msgs, gpointer data); + +static PurpleGroup * +fb_get_group(gboolean friend) +{ + PurpleBlistNode *n; + PurpleBlistNode *node; + PurpleGroup *grp; + const gchar *title; + + if (friend) { + title = _("Facebook Friends"); + } else { + title = _("Facebook Non-Friends"); + } + + grp = purple_blist_find_group(title); + + if (G_UNLIKELY(grp == NULL)) { + grp = purple_group_new(title); + node = NULL; + + for (n = purple_blist_get_root(); n != NULL; n = n->next) { + node = n; + } + + /* Append to the end of the buddy list */ + purple_blist_add_group(grp, node); + + if (!friend) { + node = PURPLE_BLIST_NODE(grp); + purple_blist_node_set_bool(node, "collapsed", TRUE); + } + } + + return grp; +} + +static void +fb_buddy_add_nonfriend(PurpleAccount *acct, FbApiUser *user) +{ + gchar uid[FB_ID_STRMAX]; + PurpleBuddy *bdy; + PurpleGroup *grp; + + FB_ID_TO_STR(user->uid, uid); + bdy = purple_buddy_new(acct, uid, user->name); + grp = fb_get_group(FALSE); + + purple_buddy_set_server_alias(bdy, user->name); + purple_blist_add_buddy(bdy, NULL, grp, NULL); +} + +static void +fb_cb_api_auth(FbApi *api, gpointer data) +{ + FbData *fata = data; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + + purple_connection_update_progress(gc, _("Fetching contacts"), 2, 4); + fb_data_save(fata); + fb_api_contacts(api); +} + +static void +fb_cb_api_connect(FbApi *api, gpointer data) +{ + FbData *fata = data; + PurpleAccount *acct; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + + fb_data_save(fata); + purple_connection_set_state(gc, PURPLE_CONNECTION_CONNECTED); + + if (purple_account_get_bool(acct, "show-unread", TRUE)) { + fb_api_unread(api); + } +} + +static void +fb_cb_api_contact(FbApi *api, FbApiUser *user, gpointer data) +{ + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + GSList *msgs; + PurpleAccount *acct; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + FB_ID_TO_STR(user->uid, uid); + + if (purple_blist_find_buddy(acct, uid) == NULL) { + fb_buddy_add_nonfriend(acct, user); + } + + msgs = fb_data_take_messages(fata, user->uid); + + if (msgs != NULL) { + fb_cb_api_messages(api, msgs, fata); + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + } +} + +static gboolean +fb_cb_sync_contacts(gpointer data) +{ + FbApi *api; + FbData *fata = data; + + api = fb_data_get_api(fata); + fb_data_clear_timeout(fata, "sync-contacts", FALSE); + fb_api_contacts(api); + return FALSE; +} + +static void +fb_cb_icon(FbDataImage *img, GError *error) +{ + const gchar *csum; + const gchar *name; + const gchar *str; + FbHttpParams *params; + gsize size; + guint8 *image; + PurpleAccount *acct; + PurpleBuddy *bdy; + + bdy = fb_data_image_get_data(img); + acct = purple_buddy_get_account(bdy); + name = purple_buddy_get_name(bdy); + + if (G_UNLIKELY(error != NULL)) { + fb_util_debug_warning("Failed to retrieve icon for %s: %s", + name, error->message); + return; + } + + str = fb_data_image_get_url(img); + params = fb_http_params_new_parse(str, TRUE); + csum = fb_http_params_get_str(params, "oh", NULL); + + image = fb_data_image_dup_image(img, &size); + purple_buddy_icons_set_for_user(acct, name, image, size, csum); + fb_http_params_free(params); +} + +static void +fb_sync_contacts_add_timeout(FbData *fata) +{ + gint sync; + PurpleConnection *gc; + PurpleAccount *acct; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + + sync = purple_account_get_int(acct, "sync-interval", 5); + + if (sync < 1) { + purple_account_set_int(acct, "sync-interval", 1); + sync = 1; + } + + sync *= 60 * 1000; + fb_data_add_timeout(fata, "sync-contacts", sync, fb_cb_sync_contacts, + fata); +} + +static void +fb_cb_api_contacts(FbApi *api, GSList *users, gboolean complete, gpointer data) +{ + const gchar *alias; + const gchar *csum; + FbApiUser *user; + FbData *fata = data; + FbId muid; + gchar uid[FB_ID_STRMAX]; + GSList *l; + GValue val = G_VALUE_INIT; + PurpleAccount *acct; + PurpleBuddy *bdy; + PurpleConnection *gc; + PurpleConnectionState state; + PurpleGroup *grp; + PurpleGroup *grpn; + PurpleStatus *status; + PurpleStatusPrimitive pstat; + PurpleStatusType *type; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + grp = fb_get_group(TRUE); + grpn = fb_get_group(FALSE); + alias = purple_account_get_private_alias(acct); + state = purple_connection_get_state(gc); + + g_value_init(&val, FB_TYPE_ID); + g_object_get_property(G_OBJECT(api), "uid", &val); + muid = g_value_get_int64(&val); + g_value_unset(&val); + + for (l = users; l != NULL; l = l->next) { + user = l->data; + FB_ID_TO_STR(user->uid, uid); + + if (G_UNLIKELY(user->uid == muid)) { + if (G_UNLIKELY(alias != NULL)) { + continue; + } + + purple_account_set_private_alias(acct, user->name); + continue; + } + + bdy = purple_blist_find_buddy(acct, uid); + + if ((bdy != NULL) && (purple_buddy_get_group(bdy) == grpn)) { + purple_blist_remove_buddy(bdy); + bdy = NULL; + } + + if (bdy == NULL) { + bdy = purple_buddy_new(acct, uid, NULL); + purple_blist_add_buddy(bdy, NULL, grp, NULL); + } + + purple_buddy_set_server_alias(bdy, user->name); + csum = purple_buddy_icons_get_checksum_for_user(bdy); + + if (!purple_strequal(csum, user->csum)) { + fb_data_image_add(fata, user->icon, fb_cb_icon, + bdy, NULL); + } + } + + fb_data_image_queue(fata); + + if (!complete) { + return; + } + + if (state != PURPLE_CONNECTION_CONNECTED) { + status = purple_account_get_active_status(acct); + type = purple_status_get_status_type(status); + pstat = purple_status_type_get_primitive(type); + + purple_connection_update_progress(gc, _("Connecting"), 3, 4); + fb_api_connect(api, pstat == PURPLE_STATUS_INVISIBLE); + } + + fb_sync_contacts_add_timeout(fata); +} + +static void +fb_cb_api_contacts_delta(FbApi *api, GSList *added, GSList *removed, gpointer data) +{ + FbApiUser *user; + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + GSList *l; + PurpleAccount *acct; + PurpleBuddy *bdy; + PurpleConnection *gc; + PurpleGroup *grp; + PurpleGroup *grpn; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + grp = fb_get_group(TRUE); + grpn = fb_get_group(FALSE); + + for (l = added; l != NULL; l = l->next) { + user = l->data; + FB_ID_TO_STR(user->uid, uid); + + bdy = purple_blist_find_buddy(acct, uid); + + if ((bdy != NULL) && (purple_buddy_get_group(bdy) == grpn)) { + purple_blist_remove_buddy(bdy); + } + + bdy = purple_buddy_new(acct, uid, NULL); + purple_blist_add_buddy(bdy, NULL, grp, NULL); + + purple_buddy_set_server_alias(bdy, user->name); + } + + for (l = removed; l != NULL; l = l->next) { + bdy = purple_blist_find_buddy(acct, l->data); + + if (bdy != NULL) { + purple_blist_remove_buddy(bdy); + } + } + + fb_sync_contacts_add_timeout(fata); +} + +static void +fb_cb_api_error(FbApi *api, GError *error, gpointer data) +{ + FbData *fata = data; + PurpleConnection *gc; + PurpleConnectionError errc; + + gc = fb_data_get_connection(fata); + + if (error->domain == FB_MQTT_SSL_ERROR) { + purple_connection_ssl_error(gc, error->code); + return; + } + + if (g_error_matches(error, FB_API_ERROR, FB_API_ERROR_QUEUE)) { + /* Save the reset data */ + fb_data_save(fata); + } + + if ((error->domain == FB_HTTP_ERROR) && + (error->code >= 400) && + (error->code <= 500)) + { + errc = PURPLE_CONNECTION_ERROR_OTHER_ERROR; + } else if (g_error_matches(error, FB_API_ERROR, FB_API_ERROR_AUTH)) { + errc = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + } else { + errc = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + } + + + if (!g_error_matches(error, FB_API_ERROR, FB_API_ERROR_NONFATAL)) { + purple_connection_error(gc, errc, error->message); + } +} + +static void +fb_cb_api_events(FbApi *api, GSList *events, gpointer data) +{ + FbData *fata = data; + FbApiEvent *event; + gchar uid[FB_ID_STRMAX]; + gchar tid[FB_ID_STRMAX]; + GHashTable *fetch; + GHashTableIter iter; + GSList *l; + PurpleAccount *acct; + PurpleChatConversation *chat; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + fetch = g_hash_table_new(fb_id_hash, fb_id_equal); + + for (l = events; l != NULL; l = l->next) { + event = l->data; + + FB_ID_TO_STR(event->tid, tid); + chat = purple_conversations_find_chat_with_account(tid, acct); + + if (chat == NULL) { + continue; + } + + FB_ID_TO_STR(event->uid, uid); + + switch (event->type) { + case FB_API_EVENT_TYPE_THREAD_TOPIC: + purple_chat_conversation_set_topic(chat, uid, + event->text); + break; + + case FB_API_EVENT_TYPE_THREAD_USER_ADDED: + if (purple_blist_find_buddy(acct, uid) == NULL) { + if (event->text) { + FbApiUser *user = fb_api_user_dup(NULL, FALSE); + user->uid = event->uid; + user->name = g_strdup(event->text); + + fb_buddy_add_nonfriend(acct, user); + + fb_api_user_free(user); + } else { + g_hash_table_insert(fetch, &event->tid, event); + break; + } + } + + purple_chat_conversation_add_user(chat, uid, NULL, 0, + TRUE); + break; + + case FB_API_EVENT_TYPE_THREAD_USER_REMOVED: + purple_chat_conversation_remove_user(chat, uid, event->text); + break; + } + } + + g_hash_table_iter_init(&iter, fetch); + + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &event)) { + fb_api_thread(api, event->tid); + } + + g_hash_table_destroy(fetch); +} + +static void +fb_cb_image(FbDataImage *img, GError *error) +{ + const gchar *url; + FbApi *api; + FbApiMessage *msg; + FbData *fata; + gsize size; + GSList *msgs = NULL; + guint id; + guint8 *image; + PurpleImage *pimg; + + fata = fb_data_image_get_fata(img); + msg = fb_data_image_get_data(img); + + if (G_UNLIKELY(error != NULL)) { + url = fb_data_image_get_url(img); + fb_util_debug_warning("Failed to retrieve image %s: %s", + url, error->message); + return; + } + + api = fb_data_get_api(fata); + image = fb_data_image_dup_image(img, &size); + pimg = purple_image_new_from_data(image, size); + id = purple_image_store_add_weak(pimg); + + g_free(msg->text); + msg->text = g_strdup_printf("", id); + msg->flags |= FB_API_MESSAGE_FLAG_DONE; + + msgs = g_slist_prepend(msgs, msg); + fb_cb_api_messages(api, msgs, fata); + g_slist_free(msgs); +} + +static void +fb_cb_api_messages(FbApi *api, GSList *msgs, gpointer data) +{ + const gchar *text; + FbApiMessage *msg; + FbData *fata = data; + gboolean isself; + gboolean mark; + gboolean open; + gboolean self; + gchar *html; + gchar tid[FB_ID_STRMAX]; + gchar uid[FB_ID_STRMAX]; + gint id; + gint64 tstamp; + GSList *l; + PurpleAccount *acct; + PurpleChatConversation *chat; + PurpleConnection *gc; + PurpleMessageFlags flags; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + mark = purple_account_get_bool(acct, "mark-read", TRUE); + open = purple_account_get_bool(acct, "group-chat-open", TRUE); + self = purple_account_get_bool(acct, "show-self", TRUE); + + for (l = msgs; l != NULL; l = l->next) { + msg = l->data; + FB_ID_TO_STR(msg->uid, uid); + + if (purple_blist_find_buddy(acct, uid) == NULL) { + msg = fb_api_message_dup(msg, TRUE); + fb_data_add_message(fata, msg); + fb_api_contact(api, msg->uid); + continue; + } + + isself = (msg->flags & FB_API_MESSAGE_FLAG_SELF) != 0; + + if (isself && !self) { + continue; + } + + flags = isself ? PURPLE_MESSAGE_SEND : PURPLE_MESSAGE_RECV; + tstamp = msg->tstamp / 1000; + + if (msg->flags & FB_API_MESSAGE_FLAG_IMAGE) { + if (!(msg->flags & FB_API_MESSAGE_FLAG_DONE)) { + msg = fb_api_message_dup(msg, TRUE); + fb_data_image_add(fata, msg->text, fb_cb_image, + msg, (GDestroyNotify) + fb_api_message_free); + fb_data_image_queue(fata); + continue; + } + + flags |= PURPLE_MESSAGE_IMAGES; + text = msg->text; + html = NULL; + } else { + html = purple_markup_escape_text(msg->text, -1); + text = html; + } + + if (msg->tid == 0) { + if (mark && !isself) { + fb_data_set_unread(fata, msg->uid, TRUE); + } + + fb_util_serv_got_im(gc, uid, text, flags, tstamp); + g_free(html); + continue; + } + + FB_ID_TO_STR(msg->tid, tid); + chat = purple_conversations_find_chat_with_account(tid, acct); + + if (chat == NULL) { + if (!open) { + g_free(html); + continue; + } + + id = fb_id_hash(&msg->tid); + purple_serv_got_joined_chat(gc, id, tid); + fb_api_thread(api, msg->tid); + } else { + id = purple_chat_conversation_get_id(chat); + } + + if (mark && !isself) { + fb_data_set_unread(fata, msg->tid, TRUE); + } + + fb_util_serv_got_chat_in(gc, id, uid, text, flags, tstamp); + g_free(html); + } +} + +static void +fb_cb_api_presences(FbApi *api, GSList *press, gpointer data) +{ + const gchar *statid; + FbApiPresence *pres; + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + GSList *l; + PurpleAccount *acct; + PurpleConnection *gc; + PurpleStatusPrimitive pstat; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + + for (l = press; l != NULL; l = l->next) { + pres = l->data; + + if (pres->active) { + pstat = PURPLE_STATUS_AVAILABLE; + } else { + pstat = PURPLE_STATUS_OFFLINE; + } + + FB_ID_TO_STR(pres->uid, uid); + statid = purple_primitive_get_id_from_type(pstat); + purple_protocol_got_user_status(acct, uid, statid, NULL); + } +} + +static void +fb_cb_api_thread(FbApi *api, FbApiThread *thrd, gpointer data) +{ + const gchar *name; + FbApiUser *user; + FbData *fata = data; + gboolean active; + gchar tid[FB_ID_STRMAX]; + gchar uid[FB_ID_STRMAX]; + gint id; + GSList *l; + PurpleAccount *acct; + PurpleChatConversation *chat; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + id = fb_id_hash(&thrd->tid); + FB_ID_TO_STR(thrd->tid, tid); + + chat = purple_conversations_find_chat_with_account(tid, acct); + + if ((chat == NULL) || purple_chat_conversation_has_left(chat)) { + chat = purple_serv_got_joined_chat(gc, id, tid); + active = FALSE; + } else { + /* If there are no users in the group chat, including + * the local user, then the group chat has yet to be + * setup by this function. As a result, any group chat + * without users is inactive. + */ + active = purple_chat_conversation_get_users_count(chat) > 0; + } + + if (!active) { + name = purple_account_get_username(acct); + purple_chat_conversation_add_user(chat, name, NULL, 0, FALSE); + } + + purple_chat_conversation_set_topic(chat, NULL, thrd->topic); + + for (l = thrd->users; l != NULL; l = l->next) { + user = l->data; + FB_ID_TO_STR(user->uid, uid); + + if (purple_chat_conversation_has_user(chat, uid)) { + continue; + } + + if (purple_blist_find_buddy(acct, uid) == NULL) { + fb_buddy_add_nonfriend(acct, user); + } + + purple_chat_conversation_add_user(chat, uid, NULL, 0, active); + } +} + +static void +fb_cb_api_thread_create(FbApi *api, FbId tid, gpointer data) +{ + FbData *fata = data; + gchar sid[FB_ID_STRMAX]; + GHashTable *table; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + FB_ID_TO_STR(tid, sid); + + table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); + g_hash_table_insert(table, "name", g_strdup(sid)); + purple_serv_join_chat(gc, table); + g_hash_table_destroy(table); +} + +static void +fb_cb_api_thread_kicked(FbApi *api, FbApiThread *thrd, gpointer data) +{ + FbData *fata = data; + gchar tid[FB_ID_STRMAX]; + PurpleAccount *acct; + PurpleConnection *gc; + PurpleChatConversation *chat; + + FB_ID_TO_STR(thrd->tid, tid); + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + chat = purple_conversations_find_chat_with_account(tid, acct); + + if (chat == NULL) { + PurpleRequestCommonParameters *cpar; + + cpar = purple_request_cpar_from_connection(gc); + purple_notify_error(gc, + _("Join a Chat"), + _("Failed to Join Chat"), + _("You have been removed from this chat"), + cpar); + return; + } + + purple_conversation_write_system_message(PURPLE_CONVERSATION(chat), + _("You have been removed from this chat"), 0); + + purple_serv_got_chat_left(gc, purple_chat_conversation_get_id(chat)); +} + +static void +fb_cb_api_threads(FbApi *api, GSList *thrds, gpointer data) +{ + const gchar *alias; + FbApiUser *user; + FbData *fata = data; + gchar tid[FB_ID_STRMAX]; + gchar uid[FB_ID_STRMAX]; + GSList *l; + GSList *m; + GString *gstr; + FbApiThread *thrd; + PurpleAccount *acct; + PurpleBuddy *bdy; + PurpleConnection *gc; + PurpleRoomlist *list; + PurpleRoomlistRoom *room; + + list = fb_data_get_roomlist(fata); + + if (G_UNLIKELY(list == NULL)) { + return; + } + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + gstr = g_string_new(NULL); + + for (l = thrds; l != NULL; l = l->next) { + thrd = l->data; + FB_ID_TO_STR(thrd->tid, tid); + g_string_truncate(gstr, 0); + + for (m = thrd->users; m != NULL; m = m->next) { + user = m->data; + FB_ID_TO_STR(user->uid, uid); + bdy = purple_blist_find_buddy(acct, uid); + + if (bdy != NULL) { + alias = purple_buddy_get_alias(bdy); + } else { + alias = user->name; + } + + if (gstr->len > 0) { + g_string_append(gstr, ", "); + } + + g_string_append(gstr, alias); + } + + room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, + tid, NULL); + purple_roomlist_room_add_field(list, room, thrd->topic); + purple_roomlist_room_add_field(list, room, gstr->str); + purple_roomlist_room_add(list, room); + } + + purple_roomlist_set_in_progress(list, FALSE); + fb_data_set_roomlist(fata, NULL); + g_string_free(gstr, TRUE); +} + +static void +fb_cb_api_typing(FbApi *api, FbApiTyping *typg, gpointer data) +{ + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + FB_ID_TO_STR(typg->uid, uid); + + if (typg->state) { + purple_serv_got_typing(gc, uid, 0, PURPLE_IM_TYPING); + } else { + purple_serv_got_typing_stopped(gc, uid); + } +} + +static void +fb_mark_read(FbData *fata, FbId id, gboolean thread) +{ + FbApi *api; + PurpleAccount *acct; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + api = fb_data_get_api(fata); + + if (!fb_data_get_unread(fata, id) || + (purple_account_get_bool(acct, "mark-read-available", FALSE) && + fb_api_is_invisible(api))) + { + return; + } + + fb_data_set_unread(fata, id, FALSE); + fb_api_read(api, id, thread); +} + +static gboolean +fb_cb_conv_read(gpointer data) +{ + const gchar *name; + FbData *fata; + FbId id; + gchar *tname; + PurpleConnection *gc; + PurpleConversation *conv = data; + + gc = purple_conversation_get_connection(conv); + fata = purple_connection_get_protocol_data(gc); + name = purple_conversation_get_name(conv); + id = FB_ID_FROM_STR(name); + + tname = g_strconcat("conv-read-", name, NULL); + fb_data_clear_timeout(fata, tname, FALSE); + g_free(tname); + + if (purple_conversation_has_focus(conv)) { + fb_mark_read(fata, id, PURPLE_IS_CHAT_CONVERSATION(conv)); + } + return FALSE; +} + +static void +fb_cb_conv_updated(PurpleConversation *conv, PurpleConversationUpdateType type, + gpointer data) +{ + const gchar *name; + const gchar *pid; + FbData *fata = data; + gchar *tname; + PurpleAccount *acct; + + acct = purple_conversation_get_account(conv); + pid = purple_account_get_protocol_id(acct); + + if ((type == PURPLE_CONVERSATION_UPDATE_UNSEEN) && + purple_strequal(pid, FB_PROTOCOL_ID) && + purple_account_get_bool(acct, "mark-read", TRUE)) + { + /* Use event loop for purple_conversation_has_focus() */ + name = purple_conversation_get_name(conv); + tname = g_strconcat("conv-read-", name, NULL); + fb_data_add_timeout(fata, tname, 1, fb_cb_conv_read, conv); + g_free(tname); + } +} + +static void +fb_cb_conv_deleting(PurpleConversation *conv, gpointer data) +{ + const gchar *name; + const gchar *pid; + FbData *fata = data; + gchar *tname; + PurpleAccount *acct; + + acct = purple_conversation_get_account(conv); + pid = purple_account_get_protocol_id(acct); + + if (!purple_strequal(pid, FB_PROTOCOL_ID)) { + return; + } + + name = purple_conversation_get_name(conv); + tname = g_strconcat("conv-read-", name, NULL); + fb_data_clear_timeout(fata, tname, TRUE); + g_free(tname); +} + +static void +fb_blist_chat_create(GSList *buddies, gpointer data) +{ + const gchar *name; + FbApi *api; + FbData *fata = data; + FbId *did; + FbId uid; + GSList *l; + GSList *uids = NULL; + PurpleConnection *gc; + PurpleRequestCommonParameters *cpar; + + gc = fb_data_get_connection(fata); + api = fb_data_get_api(fata); + + if (g_slist_length(buddies) < 2) { + cpar = purple_request_cpar_from_connection(gc); + purple_notify_error(gc, + _("Initiate Chat"), + _("Failed to Initiate Chat"), + _("At least two initial chat participants" + " are required."), + cpar); + return; + } + + for (l = buddies; l != NULL; l = l->next) { + name = purple_buddy_get_name(l->data); + uid = FB_ID_FROM_STR(name); + did = g_memdup2(&uid, sizeof uid); + uids = g_slist_prepend(uids, did); + } + + fb_api_thread_create(api, uids); + g_slist_free_full(uids, g_free); +} + +static void +fb_blist_chat_init(PurpleBlistNode *node, gpointer data) +{ + FbData *fata = data; + GSList *select = NULL; + PurpleConnection *gc; + + if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { + return; + } + + gc = fb_data_get_connection(fata); + select = g_slist_prepend(select, PURPLE_BUDDY(node)); + + fb_util_request_buddy(gc, + _("Initiate Chat"), + _("Initial Chat Participants"), + _("Select at least two initial participants."), + select, TRUE, + G_CALLBACK(fb_blist_chat_create), NULL, + fata); + g_slist_free(select); +} + +static void +fb_login(PurpleAccount *acct) +{ + const gchar *pass; + const gchar *user; + FbApi *api; + FbData *fata; + gpointer convh; + PurpleConnection *gc; + + gc = purple_account_get_connection(acct); + + fata = fb_data_new(gc); + api = fb_data_get_api(fata); + convh = purple_conversations_get_handle(); + purple_connection_set_protocol_data(gc, fata); + + g_signal_connect(api, + "auth", + G_CALLBACK(fb_cb_api_auth), + fata); + g_signal_connect(api, + "connect", + G_CALLBACK(fb_cb_api_connect), + fata); + g_signal_connect(api, + "contact", + G_CALLBACK(fb_cb_api_contact), + fata); + g_signal_connect(api, + "contacts", + G_CALLBACK(fb_cb_api_contacts), + fata); + g_signal_connect(api, + "contacts-delta", + G_CALLBACK(fb_cb_api_contacts_delta), + fata); + g_signal_connect(api, + "error", + G_CALLBACK(fb_cb_api_error), + fata); + g_signal_connect(api, + "events", + G_CALLBACK(fb_cb_api_events), + fata); + g_signal_connect(api, + "messages", + G_CALLBACK(fb_cb_api_messages), + fata); + g_signal_connect(api, + "presences", + G_CALLBACK(fb_cb_api_presences), + fata); + g_signal_connect(api, + "thread", + G_CALLBACK(fb_cb_api_thread), + fata); + g_signal_connect(api, + "thread-create", + G_CALLBACK(fb_cb_api_thread_create), + fata); + g_signal_connect(api, + "thread-kicked", + G_CALLBACK(fb_cb_api_thread_kicked), + fata); + g_signal_connect(api, + "threads", + G_CALLBACK(fb_cb_api_threads), + fata); + g_signal_connect(api, + "typing", + G_CALLBACK(fb_cb_api_typing), + fata); + + purple_signal_connect(convh, + "conversation-updated", + gc, + G_CALLBACK(fb_cb_conv_updated), + fata); + purple_signal_connect(convh, + "deleting-conversation", + gc, + G_CALLBACK(fb_cb_conv_deleting), + fata); + + if (!fb_data_load(fata) || !purple_account_get_remember_password(acct)) { + user = purple_account_get_username(acct); + pass = purple_connection_get_password(gc); + purple_connection_update_progress(gc, _("Authenticating"), + 1, 4); + fb_api_auth(api, user, pass); + return; + } + + purple_connection_update_progress(gc, _("Fetching contacts"), 2, 4); + fb_api_contacts(api); +} + +static void +fb_close(PurpleConnection *gc) +{ + FbApi *api; + FbData *fata; + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + + fb_data_save(fata); + fb_api_disconnect(api); + g_object_unref(fata); + + purple_connection_set_protocol_data(gc, NULL); + purple_signals_disconnect_by_handle(gc); +} + +static GList * +fb_status_types(PurpleAccount *acct) +{ + PurpleStatusType *type; + GList *types = NULL; + + type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, + NULL, NULL, TRUE); + types = g_list_prepend(types, type); + + /* Just a NULL state (as of now) for compatibility */ + type = purple_status_type_new(PURPLE_STATUS_AWAY, + NULL, NULL, TRUE); + types = g_list_prepend(types, type); + + type = purple_status_type_new(PURPLE_STATUS_INVISIBLE, + NULL, NULL, TRUE); + types = g_list_prepend(types, type); + + type = purple_status_type_new(PURPLE_STATUS_OFFLINE, + NULL, NULL, TRUE); + types = g_list_prepend(types, type); + + return g_list_reverse(types); +} + +static const char * +fb_list_icon(PurpleAccount *account, PurpleBuddy *buddy) +{ + return "facebook"; +} + +static void +fb_client_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *info, + gboolean full) +{ + const gchar *name; + PurplePresence *pres; + PurpleStatus *status; + + pres = purple_buddy_get_presence(buddy); + status = purple_presence_get_active_status(pres); + + if (!PURPLE_BUDDY_IS_ONLINE(buddy)) { + /* Prevent doubles statues for Offline buddies */ + /* See: pidgin_get_tooltip_text() in gtkblist.c */ + purple_notify_user_info_remove_last_item(info); + } + + name = purple_status_get_name(status); + purple_notify_user_info_add_pair_plaintext(info, _("Status"), name); +} + +static GList * +fb_client_blist_node_menu(PurpleBlistNode *node) +{ + FbData *fata; + GList *acts = NULL; + PurpleAccount *acct; + PurpleConnection *gc; + PurpleMenuAction *act; + + if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { + return NULL; + } + + acct = purple_buddy_get_account(PURPLE_BUDDY(node)); + gc = purple_account_get_connection(acct); + fata = purple_connection_get_protocol_data(gc); + + act = purple_menu_action_new(_("Initiate _Chat"), + PURPLE_CALLBACK(fb_blist_chat_init), + fata, NULL); + acts = g_list_prepend(acts, act); + + return g_list_reverse(acts); +} + +static gboolean +fb_client_offline_message(const PurpleBuddy *buddy) +{ + return TRUE; +} + +static void +fb_server_set_status(PurpleAccount *acct, PurpleStatus *status) +{ + FbApi *api; + FbData *fata; + gboolean invis; + PurpleConnection *gc; + PurpleStatusPrimitive pstat; + PurpleStatusType *type; + + gc = purple_account_get_connection(acct); + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + + type = purple_status_get_status_type(status); + pstat = purple_status_type_get_primitive(type); + invis = fb_api_is_invisible(api); + + if ((pstat == PURPLE_STATUS_INVISIBLE) && !invis) { + fb_api_connect(api, TRUE); + } else if ((pstat != PURPLE_STATUS_OFFLINE) && invis) { + fb_api_connect(api, FALSE); + } +} + +static gint +fb_im_send(PurpleConnection *gc, const gchar *who, const gchar *tmsg, + PurpleMessageFlags flags) +{ + const gchar *name; + const gchar *text; + FbApi *api; + FbData *fata; + FbId uid; + gchar *sext; + + PurpleMessage *msg = purple_message_new_outgoing(who, tmsg, flags); + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + + name = purple_message_get_recipient(msg); + uid = FB_ID_FROM_STR(name); + + text = purple_message_get_contents(msg); + sext = purple_markup_strip_html(text); + fb_api_message(api, uid, FALSE, sext); + g_free(sext); + return 1; +} + +static guint +fb_im_send_typing(PurpleConnection *gc, const gchar *name, + PurpleIMTypingState state) +{ + FbApi *api; + FbData *fata; + FbId uid; + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + uid = FB_ID_FROM_STR(name); + + fb_api_typing(api, uid, state != PURPLE_IM_NOT_TYPING); + return 0; +} + +static GList * +fb_chat_info() +{ + GList *pces = NULL; + PurpleProtocolChatEntry *pce; + + pce = g_new0(PurpleProtocolChatEntry, 1); + pce->label = _("Chat _Name:"); + pce->identifier = "name"; + pce->required = TRUE; + pces = g_list_prepend(pces, pce); + + return g_list_reverse(pces); +} + +static GHashTable * +fb_chat_info_defaults(PurpleConnection *gc, const gchar *name) +{ + GHashTable *data; + + data = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); + g_hash_table_insert(data, "name", g_strdup(name)); + + return data; +} + +static void +fb_chat_join(PurpleConnection *gc, GHashTable *data) +{ + const gchar *name; + FbApi *api; + FbData *fata; + FbId tid; + gint id; + PurpleChatConversation *chat; + PurpleRequestCommonParameters *cpar; + + name = g_hash_table_lookup(data, "name"); + g_return_if_fail(name != NULL); + + if (!FB_ID_IS_STR(name)) { + cpar = purple_request_cpar_from_connection(gc); + purple_notify_error(gc, + _("Join a Chat"), + _("Failed to Join Chat"), + _("Invalid Facebook identifier."), + cpar); + return; + } + + tid = FB_ID_FROM_STR(name); + id = fb_id_hash(&tid); + chat = purple_conversations_find_chat(gc, id); + + if ((chat != NULL) && !purple_chat_conversation_has_left(chat)) { + purple_conversation_present(PURPLE_CONVERSATION(chat)); + return; + } + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + fb_api_thread(api, tid); +} + +static gchar * +fb_chat_get_name(GHashTable *data) +{ + const gchar *name; + + name = g_hash_table_lookup(data, "name"); + g_return_val_if_fail(name != NULL, NULL); + + return g_strdup(name); +} + +static void +fb_chat_invite(PurpleConnection *gc, gint id, const gchar *msg, + const gchar *who) +{ + const gchar *name; + FbApi *api; + FbData *fata; + FbId tid; + FbId uid; + PurpleChatConversation *chat; + PurpleRequestCommonParameters *cpar; + + if (!FB_ID_IS_STR(who)) { + cpar = purple_request_cpar_from_connection(gc); + purple_notify_error(gc, + _("Invite Buddy Into Chat Room"), + _("Failed to Invite User"), + _("Invalid Facebook identifier."), + cpar); + return; + } + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + chat = purple_conversations_find_chat(gc, id); + + name = purple_conversation_get_name(PURPLE_CONVERSATION(chat)); + tid = FB_ID_FROM_STR(name); + uid = FB_ID_FROM_STR(who); + + fb_api_thread_invite(api, tid, uid); +} + +static gint +fb_chat_send(PurpleConnection *gc, gint id, const gchar *tmsg, + PurpleMessageFlags flags) +{ + const gchar *name; + const gchar *text; + FbApi *api; + FbData *fata; + FbId tid; + gchar *sext; + PurpleAccount *acct; + PurpleChatConversation *chat; + + PurpleMessage *msg = purple_message_new_outgoing(NULL, tmsg, flags); + + acct = purple_connection_get_account(gc); + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + chat = purple_conversations_find_chat(gc, id); + + name = purple_conversation_get_name(PURPLE_CONVERSATION(chat)); + tid = FB_ID_FROM_STR(name); + + text = purple_message_get_contents(msg); + sext = purple_markup_strip_html(text); + fb_api_message(api, tid, TRUE, sext); + g_free(sext); + + name = purple_account_get_username(acct); + purple_serv_got_chat_in(gc, id, name, + purple_message_get_flags(msg), + purple_message_get_contents(msg), + time(NULL)); + return 0; +} + +static void +fb_chat_set_topic(PurpleConnection *gc, gint id, const gchar *topic) +{ + const gchar *name; + FbApi *api; + FbData *fata; + FbId tid; + PurpleChatConversation *chat; + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + chat = purple_conversations_find_chat(gc, id); + + name = purple_conversation_get_name(PURPLE_CONVERSATION(chat)); + tid = FB_ID_FROM_STR(name); + fb_api_thread_topic(api, tid, topic); +} + +static PurpleRoomlist * +fb_roomlist_get_list(PurpleConnection *gc) +{ + FbApi *api; + FbData *fata; + GList *flds = NULL; + PurpleAccount *acct; + PurpleRoomlist *list; + PurpleRoomlistField *fld; + + fata = purple_connection_get_protocol_data(gc); + list = fb_data_get_roomlist(fata); + g_return_val_if_fail(list == NULL, NULL); + + api = fb_data_get_api(fata); + acct = purple_connection_get_account(gc); + list = purple_roomlist_new(acct); + fb_data_set_roomlist(fata, list); + + fld = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, + _("Topic"), "topic", FALSE); + flds = g_list_prepend(flds, fld); + + fld = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, + _("Users"), "users", FALSE); + flds = g_list_prepend(flds, fld); + + flds = g_list_reverse(flds); + purple_roomlist_set_fields(list, flds); + + purple_roomlist_set_in_progress(list, TRUE); + fb_api_threads(api); + return list; +} + +static void +fb_roomlist_cancel(PurpleRoomlist *list) +{ + FbData *fata; + PurpleAccount *acct; + PurpleConnection *gc; + PurpleRoomlist *cist; + + acct = purple_roomlist_get_account(list); + gc = purple_account_get_connection(acct); + fata = purple_connection_get_protocol_data(gc); + cist = fb_data_get_roomlist(fata); + + if (G_LIKELY(cist == list)) { + fb_data_set_roomlist(fata, NULL); + } + + purple_roomlist_set_in_progress(list, FALSE); + g_object_unref(list); +} + +static PurpleCmdRet +fb_cmd_kick(PurpleConversation *conv, const gchar *cmd, gchar **args, + gchar **error, gpointer data) +{ + const gchar *name; + FbApi *api; + FbData *fata; + FbId tid; + FbId uid; + GError *err = NULL; + PurpleAccount *acct; + PurpleBuddy *bdy; + PurpleConnection *gc; + PurpleChatConversation *chat; + + g_return_val_if_fail(PURPLE_IS_CHAT_CONVERSATION(conv), + PURPLE_CMD_RET_FAILED); + + gc = purple_conversation_get_connection(conv); + acct = purple_connection_get_account(gc); + chat = PURPLE_CHAT_CONVERSATION(conv); + bdy = fb_util_account_find_buddy(acct, chat, args[0], &err); + + if (err != NULL) { + *error = g_strdup_printf(_("%s."), err->message); + g_error_free(err); + return PURPLE_CMD_RET_FAILED; + } + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + + name = purple_conversation_get_name(conv); + tid = FB_ID_FROM_STR(name); + + name = purple_buddy_get_name(bdy); + uid = FB_ID_FROM_STR(name); + + fb_api_thread_remove(api, tid, uid); + return PURPLE_CMD_RET_OK; +} + +static PurpleCmdRet +fb_cmd_leave(PurpleConversation *conv, const gchar *cmd, gchar **args, + gchar **error, gpointer data) +{ + const gchar *name; + FbApi *api; + FbData *fata; + FbId tid; + gint id; + PurpleConnection *gc; + PurpleChatConversation *chat; + + g_return_val_if_fail(PURPLE_IS_CHAT_CONVERSATION(conv), + PURPLE_CMD_RET_FAILED); + + gc = purple_conversation_get_connection(conv); + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + + chat = PURPLE_CHAT_CONVERSATION(conv); + id = purple_chat_conversation_get_id(chat); + + name = purple_conversation_get_name(conv); + tid = FB_ID_FROM_STR(name); + + purple_serv_got_chat_left(gc, id); + fb_api_thread_remove(api, tid, 0); + return PURPLE_CMD_RET_OK; +} + +static void +fb_cmds_register(void) +{ + PurpleCmdId id; + + static PurpleCmdFlag cflags = + PURPLE_CMD_FLAG_CHAT | + PURPLE_CMD_FLAG_PROTOCOL_ONLY; + + g_return_if_fail(fb_cmds == NULL); + + id = purple_cmd_register("kick", "s", PURPLE_CMD_P_PROTOCOL, cflags, + "prpl-facebook", fb_cmd_kick, + _("kick: Kick someone from the chat"), + NULL); + fb_cmds = g_slist_prepend(fb_cmds, GUINT_TO_POINTER(id)); + + id = purple_cmd_register("leave", "", PURPLE_CMD_P_PROTOCOL, cflags, + "prpl-facebook", fb_cmd_leave, + _("leave: Leave the chat"), + NULL); + fb_cmds = g_slist_prepend(fb_cmds, GUINT_TO_POINTER(id)); +} + +static void +fb_cmds_unregister_free(gpointer data) +{ + PurpleCmdId id = GPOINTER_TO_UINT(data); + purple_cmd_unregister(id); +} + +static void +fb_cmds_unregister(void) +{ + g_slist_free_full(fb_cmds, fb_cmds_unregister_free); +} + +static gboolean +plugin_load(PurplePlugin *plugin) +{ + fb_cmds_register(); + _purple_socket_init(); + purple_http_init(); + return TRUE; +} + +static gboolean +plugin_unload(PurplePlugin *plugin) +{ + fb_cmds_unregister(); + purple_http_uninit(); + _purple_socket_uninit(); + return TRUE; +} + +G_MODULE_EXPORT gboolean +purple_init_plugin(PurplePlugin *plugin); + +G_MODULE_EXPORT gboolean +purple_init_plugin(PurplePlugin *plugin) +{ + GList *opts = NULL; + PurpleAccountOption *opt; + + static gboolean inited = FALSE; + static PurplePluginInfo info; + static PurplePluginProtocolInfo pinfo; + + (void) fb_protocol; + plugin->info = &info; + + if (G_LIKELY(inited)) { + return purple_plugin_register(plugin); + } + + memset(&info, 0, sizeof info); + memset(&pinfo, 0, sizeof pinfo); + + info.magic = PURPLE_PLUGIN_MAGIC; + info.major_version = PURPLE_MAJOR_VERSION; + info.minor_version = PURPLE_MINOR_VERSION; + info.type = PURPLE_PLUGIN_PROTOCOL; + info.priority = PURPLE_PRIORITY_DEFAULT; + info.id = FB_PROTOCOL_ID; + info.name = "Facebook"; + info.version = PACKAGE_VERSION; + info.summary = N_("Facebook Protocol Plugin"); + info.description = N_("Facebook Protocol Plugin"); + info.homepage = PACKAGE_URL; + info.load = plugin_load; + info.unload = plugin_unload; + info.extra_info = &pinfo; + + pinfo.options = OPT_PROTO_CHAT_TOPIC; + pinfo.list_icon = fb_list_icon; + pinfo.tooltip_text = fb_client_tooltip_text; + pinfo.status_types = fb_status_types; + pinfo.blist_node_menu = fb_client_blist_node_menu; + pinfo.chat_info = fb_chat_info; + pinfo.chat_info_defaults = fb_chat_info_defaults; + pinfo.login = fb_login; + pinfo.close = fb_close; + pinfo.send_im = fb_im_send; + pinfo.send_typing = fb_im_send_typing; + pinfo.set_status = fb_server_set_status; + pinfo.join_chat = fb_chat_join; + pinfo.get_chat_name = fb_chat_get_name; + pinfo.chat_invite = fb_chat_invite; + pinfo.chat_send = fb_chat_send; + pinfo.set_chat_topic = fb_chat_set_topic; + pinfo.roomlist_get_list = fb_roomlist_get_list; + pinfo.roomlist_cancel = fb_roomlist_cancel; + pinfo.offline_message = fb_client_offline_message; + pinfo.struct_size = sizeof pinfo; + + opt = purple_account_option_int_new(_("Buddy list sync interval"), + "sync-interval", 5); + opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Mark messages as read on focus"), + "mark-read", TRUE); + opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Mark messages as read only when available"), + "mark-read-available", FALSE); + opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Show self messages"), + "show-self", TRUE); + opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Show unread messages"), + "show-unread", TRUE); + opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Open new group chats with " + "incoming messages"), + "group-chat-open", TRUE); + opts = g_list_prepend(opts, opt); + pinfo.protocol_options = g_list_reverse(opts); + + inited = TRUE; + return purple_plugin_register(plugin); +} diff --git a/pidgin/libpurple/protocols/facebook/facebook.h b/pidgin/libpurple/protocols/facebook/facebook.h new file mode 100644 index 00000000..aa8b3c34 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/facebook.h @@ -0,0 +1,34 @@ +/* purple + * + * 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 + */ + +#ifndef _FACEBOOK_H_ +#define _FACEBOOK_H_ + +#include + +/** + * FB_PROTOCOL_ID: + * + * The Facebook protocol identifier. + */ +#define FB_PROTOCOL_ID "prpl-facebook" + +#endif /* _FACEBOOK_H_ */ diff --git a/pidgin/libpurple/protocols/facebook/http.c b/pidgin/libpurple/protocols/facebook/http.c new file mode 100644 index 00000000..cd9876cf --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/http.c @@ -0,0 +1,438 @@ +/* purple + * + * 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 + +#include "http.h" + +struct _FbHttpConns +{ + GHashTable *cons; + gboolean canceled; +}; + +GQuark +fb_http_error_quark(void) +{ + static GQuark q = 0; + + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-http-error-quark"); + } + + return q; +} + +FbHttpConns * +fb_http_conns_new(void) +{ + FbHttpConns *cons; + + cons = g_new0(FbHttpConns, 1); + cons->cons = g_hash_table_new(g_direct_hash, g_direct_equal); + return cons; +} + +void +fb_http_conns_free(FbHttpConns *cons) +{ + g_return_if_fail(cons != NULL); + + g_hash_table_destroy(cons->cons); + g_free(cons); +} + +void +fb_http_conns_cancel_all(FbHttpConns *cons) +{ + GHashTableIter iter; + gpointer con; + + g_return_if_fail(cons != NULL); + g_return_if_fail(!cons->canceled); + + cons->canceled = TRUE; + g_hash_table_iter_init(&iter, cons->cons); + + while (g_hash_table_iter_next(&iter, &con, NULL)) { + g_hash_table_iter_remove(&iter); + purple_http_conn_cancel(con); + } +} + +gboolean +fb_http_conns_is_canceled(FbHttpConns *cons) +{ + g_return_val_if_fail(cons != NULL, TRUE); + return cons->canceled; +} + +void +fb_http_conns_add(FbHttpConns *cons, PurpleHttpConnection *con) +{ + g_return_if_fail(cons != NULL); + g_return_if_fail(!cons->canceled); + g_hash_table_replace(cons->cons, con, con); +} + +void +fb_http_conns_remove(FbHttpConns *cons, PurpleHttpConnection *con) +{ + g_return_if_fail(cons != NULL); + g_return_if_fail(!cons->canceled); + g_hash_table_remove(cons->cons, con); +} + +void +fb_http_conns_reset(FbHttpConns *cons) +{ + g_return_if_fail(cons != NULL); + cons->canceled = FALSE; + g_hash_table_remove_all(cons->cons); +} + +gboolean +fb_http_error_chk(PurpleHttpResponse *res, GError **error) +{ + const gchar *msg; + gint code; + + if (purple_http_response_is_successful(res)) { + return TRUE; + } + + msg = purple_http_response_get_error(res); + code = purple_http_response_get_code(res); + g_set_error(error, FB_HTTP_ERROR, code, "%s", msg); + return FALSE; +} + +FbHttpParams * +fb_http_params_new(void) +{ + return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); +} + +FbHttpParams * +fb_http_params_new_parse(const gchar *data, gboolean isurl) +{ + const gchar *tail; + gchar *key; + gchar **ps; + gchar *val; + guint i; + FbHttpParams *params; + + params = fb_http_params_new(); + + if (data == NULL) { + return params; + } + + if (isurl) { + data = strchr(data, '?'); + + if (data == NULL) { + return params; + } + + tail = strchr(++data, '#'); + + if (tail != NULL) { + data = g_strndup(data, tail - data); + } else { + data = g_strdup(data); + } + } + + ps = g_strsplit(data, "&", 0); + + for (i = 0; ps[i] != NULL; i++) { + key = ps[i]; + val = strchr(ps[i], '='); + + if (val == NULL) { + continue; + } + + *(val++) = 0; + key = g_uri_unescape_string(key, NULL); + val = g_uri_unescape_string(val, NULL); + g_hash_table_replace(params, key, val); + } + + if (isurl) { + g_free((gchar *) data); + } + + g_strfreev(ps); + return params; +} + +void +fb_http_params_free(FbHttpParams *params) +{ + g_hash_table_destroy(params); +} + +gchar * +fb_http_params_close(FbHttpParams *params, const gchar *url) +{ + GHashTableIter iter; + gpointer key; + gpointer val; + GString *ret; + + g_hash_table_iter_init(&iter, params); + ret = g_string_new(NULL); + + while (g_hash_table_iter_next(&iter, &key, &val)) { + if (val == NULL) { + g_hash_table_iter_remove(&iter); + continue; + } + + if (ret->len > 0) { + g_string_append_c(ret, '&'); + } + + g_string_append_uri_escaped(ret, key, NULL, TRUE); + g_string_append_c(ret, '='); + g_string_append_uri_escaped(ret, val, NULL, TRUE); + } + + if (url != NULL) { + g_string_prepend_c(ret, '?'); + g_string_prepend(ret, url); + } + + fb_http_params_free(params); + return g_string_free(ret, FALSE); +} + +static const gchar * +fb_http_params_get(FbHttpParams *params, const gchar *name, GError **error) +{ + const gchar *ret; + + ret = g_hash_table_lookup(params, name); + + if (ret == NULL) { + g_set_error(error, FB_HTTP_ERROR, FB_HTTP_ERROR_NOMATCH, + _("No matches for %s"), name); + return NULL; + } + + return ret; +} + +gboolean +fb_http_params_get_bool(FbHttpParams *params, const gchar *name, + GError **error) +{ + const gchar *val; + + val = fb_http_params_get(params, name, error); + + if (val == NULL) { + return FALSE; + } + + return g_ascii_strcasecmp(val, "TRUE") == 0; +} + +gdouble +fb_http_params_get_dbl(FbHttpParams *params, const gchar *name, + GError **error) +{ + const gchar *val; + + val = fb_http_params_get(params, name, error); + + if (val == NULL) { + return 0.0; + } + + return g_ascii_strtod(val, NULL); +} + +gint64 +fb_http_params_get_int(FbHttpParams *params, const gchar *name, + GError **error) +{ + const gchar *val; + + val = fb_http_params_get(params, name, error); + + if (val == NULL) { + return 0; + } + + return g_ascii_strtoll(val, NULL, 10); +} + +const gchar * +fb_http_params_get_str(FbHttpParams *params, const gchar *name, + GError **error) +{ + return fb_http_params_get(params, name, error); +} + +gchar * +fb_http_params_dup_str(FbHttpParams *params, const gchar *name, + GError **error) +{ + const gchar *str; + + str = fb_http_params_get(params, name, error); + return g_strdup(str); +} + +static void +fb_http_params_set(FbHttpParams *params, const gchar *name, gchar *value) +{ + gchar *key; + + key = g_strdup(name); + g_hash_table_replace(params, key, value); +} + +void +fb_http_params_set_bool(FbHttpParams *params, const gchar *name, + gboolean value) +{ + gchar *val; + + val = g_strdup(value ? "true" : "false"); + fb_http_params_set(params, name, val); +} + +void +fb_http_params_set_dbl(FbHttpParams *params, const gchar *name, gdouble value) +{ + gchar *val; + + val = g_strdup_printf("%f", value); + fb_http_params_set(params, name, val); +} + +void +fb_http_params_set_int(FbHttpParams *params, const gchar *name, gint64 value) +{ + gchar *val; + + val = g_strdup_printf("%" G_GINT64_FORMAT, value); + fb_http_params_set(params, name, val); +} + +void +fb_http_params_set_str(FbHttpParams *params, const gchar *name, + const gchar *value) +{ + gchar *val; + + val = g_strdup(value); + fb_http_params_set(params, name, val); +} + +void +fb_http_params_set_strf(FbHttpParams *params, const gchar *name, + const gchar *format, ...) +{ + gchar *val; + va_list ap; + + va_start(ap, format); + val = g_strdup_vprintf(format, ap); + va_end(ap); + + fb_http_params_set(params, name, val); +} + +gboolean +fb_http_urlcmp(const gchar *url1, const gchar *url2, gboolean protocol) +{ + const gchar *str1; + const gchar *str2; + gboolean ret = TRUE; + gint int1; + gint int2; + guint i; + PurpleHttpURL *purl1; + PurpleHttpURL *purl2; + + static const gchar * (*funcs[]) (const PurpleHttpURL *url) = { + /* Always first so it can be skipped */ + purple_http_url_get_protocol, + + purple_http_url_get_fragment, + purple_http_url_get_host, + purple_http_url_get_password, + purple_http_url_get_path, + purple_http_url_get_username + }; + + if ((url1 == NULL) || (url2 == NULL)) { + return url1 == url2; + } + + if (strstr(url1, url2) != NULL || strstr(url2, url1) != NULL) { + return TRUE; + } + + purl1 = purple_http_url_parse(url1); + + if (purl1 == NULL) { + return g_ascii_strcasecmp(url1, url2) == 0; + } + + purl2 = purple_http_url_parse(url2); + + if (purl2 == NULL) { + purple_http_url_free(purl1); + return g_ascii_strcasecmp(url1, url2) == 0; + } + + for (i = protocol ? 0 : 1; i < G_N_ELEMENTS(funcs); i++) { + str1 = funcs[i](purl1); + str2 = funcs[i](purl2); + + if (!purple_strequal(str1, str2)) { + ret = FALSE; + break; + } + } + + if (ret && protocol) { + int1 = purple_http_url_get_port(purl1); + int2 = purple_http_url_get_port(purl2); + + if (int1 != int2) { + ret = FALSE; + } + } + + purple_http_url_free(purl1); + purple_http_url_free(purl2); + return ret; +} diff --git a/pidgin/libpurple/protocols/facebook/http.h b/pidgin/libpurple/protocols/facebook/http.h new file mode 100644 index 00000000..8419ddae --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/http.h @@ -0,0 +1,371 @@ +/* purple + * + * 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 + */ + +#ifndef _FACEBOOK_HTTP_H_ +#define _FACEBOOK_HTTP_H_ + +/** + * SECTION:http + * @section_id: facebook-http + * @short_description: http.h + * @title: HTTP Utilities + * + * The HTTP utilities. + */ + +#include + +#include + +/** + * FB_HTTP_ERROR: + * + * The #GQuark of the domain of HTTP errors. + */ +#define FB_HTTP_ERROR fb_http_error_quark() + +/** + * FbHttpConns: + * + * Represents a set of #PurpleHttpConnection. + */ +typedef struct _FbHttpConns FbHttpConns; + +/** + * FbHttpParams: + * + * Represents a set of key/value HTTP parameters. + */ +typedef GHashTable FbHttpParams; + +/** + * FbHttpError: + * @FB_HTTP_ERROR_SUCCESS: There is no error. + * @FB_HTTP_ERROR_NOMATCH: The name does not match anything. + * + * The error codes for the #FB_HTTP_ERROR domain. + */ +typedef enum +{ + FB_HTTP_ERROR_SUCCESS = 0, + FB_HTTP_ERROR_NOMATCH +} FbHttpError; + +/** + * fb_http_error_quark: + * + * Gets the #GQuark of the domain of HTTP errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_http_error_quark(void); + +/** + * fb_http_conns_new: + * + * Creates a new #FbHttpConns. The returned #FbHttpConns should be + * freed with #fb_http_conns_free() when no longer needed. + * + * Returns: The new #FbHttpConns. + */ +FbHttpConns * +fb_http_conns_new(void); + +/** + * fb_http_conns_free: + * @cons: The #FbHttpConns. + * + * Frees all memory used by the #FbHttpConns. This will *not* cancel + * the any of the added #PurpleHttpConnection. + */ +void +fb_http_conns_free(FbHttpConns *cons); + +/** + * fb_http_conns_cancel_all: + * @cons: The #FbHttpConns. + * + * Cancels each #PurpleHttpConnection in the #FbHttpConns. + */ +void +fb_http_conns_cancel_all(FbHttpConns *cons); + +/** + * fb_http_conns_is_canceled: + * @cons: The #FbHttpConns. + * + * Determines if the #FbHttpConns has been canceled. + * + * Returns: #TRUE if it has been canceled, otherwise #FALSE. + */ +gboolean +fb_http_conns_is_canceled(FbHttpConns *cons); + +/** + * fb_http_conns_add: + * @cons: The #FbHttpConns. + * @con: The #PurpleHttpConnection. + * + * Adds a #PurpleHttpConnection to the #FbHttpConns. + */ +void +fb_http_conns_add(FbHttpConns *cons, PurpleHttpConnection *con); + +/** + * fb_http_conns_remove: + * @cons: The #FbHttpConns. + * @con: The #PurpleHttpConnection. + * + * Removes a #PurpleHttpConnection from the #FbHttpConns. + */ +void +fb_http_conns_remove(FbHttpConns *cons, PurpleHttpConnection *con); + +/** + * fb_http_conns_reset: + * @cons: The #FbHttpConns. + * + * Resets the #FbHttpConns. This removes each #PurpleHttpConnection + * from the #FbHttpConns *without* canceling it. This allows the the + * #FbHttpConns to be reused. + */ +void +fb_http_conns_reset(FbHttpConns *cons); + +/** + * fb_http_error_chk: + * @res: The #PurpleHttpResponse. + * @error: The return location for the #GError or #NULL. + * + * Checks a #PurpleHttpResponse for success. This optionally assigns an + * appropriate #GError upon failure. + * + * Returns: #TRUE if the request was successful, otherwise #FALSE. + */ +gboolean +fb_http_error_chk(PurpleHttpResponse *res, GError **error); + +/** + * fb_http_params_new: + * + * Creates a new #FbHttpParams. The returned #FbHttpParams should be + * freed with #fb_http_params_free() when no longer needed. Optionally, + * instead of freeing, the returned #FbHttpParams can be closed with + * #fb_http_params_close(). + * + * Returns: The new #FbHttpParams. + */ +FbHttpParams * +fb_http_params_new(void); + +/** + * fb_http_params_new_parse: + * @data: The string containing HTTP parameters. + * @isurl: #TRUE if @data is a URL, otherwise #FALSE. + * + * Creates a new #FbHttpParams. This parses the #FbHttpParams from a + * string, which can be a URL. The returned #FbHttpParams should be + * freed with #fb_http_params_free() when no longer needed. Optionally, + * instead of freeing, the returned #FbHttpParams can be closed with + * #fb_http_params_close(). + * + * Returns: The new #FbHttpParams. + */ +FbHttpParams * +fb_http_params_new_parse(const gchar *data, gboolean isurl); + +/** + * fb_http_params_free: + * @params: The #FbHttpParams. + * + * Frees all memory used by the #FbHttpParams. + */ +void +fb_http_params_free(FbHttpParams *params); + +/** + * fb_http_params_close: + * @params: The #FbHttpParams. + * @url: The URL or #NULL. + * + * Closes the #FbHttpParams by returning a string representing the HTTP + * parameters. If @url is non-#NULL, then the parameters are appended + * to the value of @url. This frees the #FbHttpParams. The returned + * string should be freed with #g_free() when no longer needed. + * + * Returns: The string representation of the HTTP parameters. + */ +gchar * +fb_http_params_close(FbHttpParams *params, const gchar *url); + +/** + * fb_http_params_get_bool: + * @params: The #FbHttpParams. + * @name: The parameter name. + * @error: The return location for the #GError or #NULL. + * + * Gets a boolean value from the #FbHttpParams. This optionally assigns + * an appropriate #GError upon failure. + * + * Return: The boolean value. + */ +gboolean +fb_http_params_get_bool(FbHttpParams *params, const gchar *name, + GError **error); + +/** + * fb_http_params_get_dbl: + * @params: The #FbHttpParams. + * @name: The parameter name. + * @error: The return location for the #GError or #NULL. + * + * Gets a floating point value from the #FbHttpParams. This optionally + * assigns an appropriate #GError upon failure. + * + * Return: The floating point value. + */ +gdouble +fb_http_params_get_dbl(FbHttpParams *params, const gchar *name, + GError **error); + +/** + * fb_http_params_get_int: + * @params: The #FbHttpParams. + * @name: The parameter name. + * @error: The return location for the #GError or #NULL. + * + * Gets an integer value from the #FbHttpParams. This optionally + * assigns an appropriate #GError upon failure. + * + * Return: The integer value. + */ +gint64 +fb_http_params_get_int(FbHttpParams *params, const gchar *name, + GError **error); + +/** + * fb_http_params_get_str: + * @params: The #FbHttpParams. + * @name: The parameter name. + * @error: The return location for the #GError or #NULL. + * + * Gets a string value from the #FbHttpParams. This optionally assigns + * an appropriate #GError upon failure. + * + * Return: The string value. + */ +const gchar * +fb_http_params_get_str(FbHttpParams *params, const gchar *name, + GError **error); + +/** + * fb_http_params_dup_str: + * @params: The #FbHttpParams. + * @name: The parameter name. + * @error: The return location for the #GError or #NULL. + * + * Gets a duplicated string value from the #FbHttpParams. This + * optionally assigns an appropriate #GError upon failure. The returned + * string should be freed with #g_free() when no longer needed. + * + * Return: The duplicated string value. + */ +gchar * +fb_http_params_dup_str(FbHttpParams *params, const gchar *name, + GError **error); + +/** + * fb_http_params_set_bool: + * @params: The #FbHttpParams. + * @name: The parameter name. + * @value: The value. + * + * Sets a boolean value to the #FbHttpParams. + */ +void +fb_http_params_set_bool(FbHttpParams *params, const gchar *name, + gboolean value); + +/** + * fb_http_params_set_dbl: + * @params: The #FbHttpParams. + * @name: The parameter name. + * @value: The value. + * + * Sets a floating point value to the #FbHttpParams. + */ +void +fb_http_params_set_dbl(FbHttpParams *params, const gchar *name, gdouble value); + +/** + * fb_http_params_set_int: + * @params: The #FbHttpParams. + * @name: The parameter name. + * @value: The value. + * + * Sets an integer value to the #FbHttpParams. + */ +void +fb_http_params_set_int(FbHttpParams *params, const gchar *name, gint64 value); + +/** + * fb_http_params_set_str: + * @params: The #FbHttpParams. + * @name: The parameter name. + * @value: The value. + * + * Sets a string value to the #FbHttpParams. + */ +void +fb_http_params_set_str(FbHttpParams *params, const gchar *name, + const gchar *value); + +/** + * fb_http_params_set_strf: + * @params: The #FbHttpParams. + * @name: The parameter name. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Sets a formatted string value to the #FbHttpParams. + */ +void +fb_http_params_set_strf(FbHttpParams *params, const gchar *name, + const gchar *format, ...) + G_GNUC_PRINTF(3, 4); + +/** + * fb_http_urlcmp: + * @url1: The first URL. + * @url2: The second URL. + * @protocol: #TRUE to match the protocols, otherwise #FALSE. + * + * Compares two URLs. This is more reliable than just comparing two URL + * strings, as it avoids casing in some areas, while not in others. It + * can also, optionally, ignore the matching of the URL protocol. + * + * Returns: #TRUE if the URLs match, otherwise #FALSE. + */ +gboolean +fb_http_urlcmp(const gchar *url1, const gchar *url2, gboolean protocol); + +#endif /* _FACEBOOK_HTTP_H_ */ diff --git a/pidgin/libpurple/protocols/facebook/id.h b/pidgin/libpurple/protocols/facebook/id.h new file mode 100644 index 00000000..00b4bd7a --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/id.h @@ -0,0 +1,131 @@ +/* purple + * + * 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 + */ + +#ifndef _FACEBOOK_ID_H_ +#define _FACEBOOK_ID_H_ + +/** + * SECTION:id + * @section_id: facebook-id + * @short_description: id.h + * @title: Facebook Identifier + * + * The Facebook identifier utilities. + */ + +#include +#include + +#include "util.h" + +/** + * FB_ID_FORMAT: + * + * The format specifier for printing and scanning an #FbId. + */ +#define FB_ID_FORMAT G_GINT64_FORMAT + +/** + * FB_ID_MODIFIER: + * + * The length modifier for printing an #FbId. + */ +#define FB_ID_MODIFIER G_GINT64_MODIFIER + +/** + * FB_ID_STRMAX: + * + * The maximum length, including a null-terminating character, of the + * string representation of an #FbId. + */ +#define FB_ID_STRMAX 21 + +/** + * FB_TYPE_ID: + * + * The #GType of an #FbId. + */ +#define FB_TYPE_ID G_TYPE_INT64 + +/** + * FB_ID_CONSTANT: + * @v: The value. + * + * Inserts a literal #FbId into source code. + * + * Return: The literal #FbId value. + */ +#define FB_ID_CONSTANT(v) G_GINT64_CONSTANT(v) + +/** + * FB_ID_FROM_STR: + * @s: The string value. + * + * Converts a string to an #FbId. + * + * Return: The converted #FbId value. + */ +#define FB_ID_FROM_STR(s) g_ascii_strtoll(s, NULL, 10) + +/** + * FB_ID_IS_STR: + * @s: The string value. + * + * Determines if a string is an #FbId. + * + * Return: #TRUE if the string is an #FbId, otherwise #FALSE. + */ +#define FB_ID_IS_STR(s) fb_util_strtest(s, G_ASCII_DIGIT) + +/** + * FB_ID_TO_STR: + * @i: The #FbId. + * @s: The string buffer. + * + * Converts an #FbId to a string. The buffer should be at least the + * size of #FB_ID_STRMAX. + * + * Return: The converted string value. + */ +#define FB_ID_TO_STR(i, s) g_sprintf(s, "%" FB_ID_FORMAT, (FbId) i) + +/** + * fb_id_equal: + * + * Compares the values of two #FbId's for equality. See #g_int64_equal. + */ +#define fb_id_equal g_int64_equal + +/** + * fb_id_hash: + * + * Converts a pointer to a #FbId hash value. See #g_int64_hash. + */ +#define fb_id_hash g_int64_hash + +/** + * FbId: + * + * Represents a numeric Facebook identifier. + */ +typedef gint64 FbId; + +#endif /* _FACEBOOK_ID_H_ */ diff --git a/pidgin/libpurple/protocols/facebook/json.c b/pidgin/libpurple/protocols/facebook/json.c new file mode 100644 index 00000000..448c5b2e --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/json.c @@ -0,0 +1,677 @@ +/* purple + * + * 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 +#include + +#include "json.h" +#include "glibcompat.h" +#include "util.h" + +typedef struct _FbJsonValue FbJsonValue; + +struct _FbJsonValue +{ + const gchar *expr; + FbJsonType type; + gboolean required; + GValue value; +}; + +struct _FbJsonValuesPrivate +{ + JsonNode *root; + GQueue *queue; + GList *next; + + gboolean isarray; + JsonArray *array; + guint index; + + GError *error; +}; + +G_DEFINE_TYPE_WITH_CODE(FbJsonValues, fb_json_values, G_TYPE_OBJECT, G_ADD_PRIVATE(FbJsonValues)); + +static void +fb_json_values_dispose(GObject *obj) +{ + FbJsonValue *value; + FbJsonValuesPrivate *priv = FB_JSON_VALUES(obj)->priv; + + while (!g_queue_is_empty(priv->queue)) { + value = g_queue_pop_head(priv->queue); + + if (G_IS_VALUE(&value->value)) { + g_value_unset(&value->value); + } + + g_free(value); + } + + if (priv->array != NULL) { + json_array_unref(priv->array); + } + + if (priv->error != NULL) { + g_error_free(priv->error); + } + + g_queue_free(priv->queue); +} + +static void +fb_json_values_class_init(FbJsonValuesClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); + + gklass->dispose = fb_json_values_dispose; + g_type_class_add_private(klass, sizeof (FbJsonValuesPrivate)); +} + +static void +fb_json_values_init(FbJsonValues *values) +{ + FbJsonValuesPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(values, FB_TYPE_JSON_VALUES, + FbJsonValuesPrivate); + values->priv = priv; + + priv->queue = g_queue_new(); +} + +GQuark +fb_json_error_quark(void) +{ + static GQuark q = 0; + + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-json-error-quark"); + } + + return q; +} + +JsonBuilder * +fb_json_bldr_new(JsonNodeType type) +{ + JsonBuilder *bldr; + + bldr = json_builder_new(); + + switch (type) { + case JSON_NODE_ARRAY: + fb_json_bldr_arr_begin(bldr, NULL); + break; + + case JSON_NODE_OBJECT: + fb_json_bldr_obj_begin(bldr, NULL); + break; + + default: + break; + } + + return bldr; +} + +gchar * +fb_json_bldr_close(JsonBuilder *bldr, JsonNodeType type, gsize *size) +{ + gchar *ret; + JsonGenerator *genr; + JsonNode *root; + + switch (type) { + case JSON_NODE_ARRAY: + fb_json_bldr_arr_end(bldr); + break; + + case JSON_NODE_OBJECT: + fb_json_bldr_obj_end(bldr); + break; + + default: + break; + } + + genr = json_generator_new(); + root = json_builder_get_root(bldr); + + json_generator_set_root(genr, root); + ret = json_generator_to_data(genr, size); + + json_node_free(root); + g_object_unref(genr); + g_object_unref(bldr); + + return ret; +} + +void +fb_json_bldr_arr_begin(JsonBuilder *bldr, const gchar *name) +{ + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } + + json_builder_begin_array(bldr); +} + +void +fb_json_bldr_arr_end(JsonBuilder *bldr) +{ + json_builder_end_array(bldr); +} + +void +fb_json_bldr_obj_begin(JsonBuilder *bldr, const gchar *name) +{ + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } + + json_builder_begin_object(bldr); +} + +void +fb_json_bldr_obj_end(JsonBuilder *bldr) +{ + json_builder_end_object(bldr); +} + +void +fb_json_bldr_add_bool(JsonBuilder *bldr, const gchar *name, gboolean value) +{ + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } + + json_builder_add_boolean_value(bldr, value); +} + +void +fb_json_bldr_add_dbl(JsonBuilder *bldr, const gchar *name, gdouble value) +{ + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } + + json_builder_add_double_value(bldr, value); +} + +void +fb_json_bldr_add_int(JsonBuilder *bldr, const gchar *name, gint64 value) +{ + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } + + json_builder_add_int_value(bldr, value); +} + +void +fb_json_bldr_add_str(JsonBuilder *bldr, const gchar *name, const gchar *value) +{ + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } + + json_builder_add_string_value(bldr, value); +} + +void +fb_json_bldr_add_strf(JsonBuilder *bldr, const gchar *name, + const gchar *format, ...) +{ + gchar *value; + va_list ap; + + va_start(ap, format); + value = g_strdup_vprintf(format, ap); + va_end(ap); + + fb_json_bldr_add_str(bldr, name, value); + g_free(value); +} + +JsonNode * +fb_json_node_new(const gchar *data, gssize size, GError **error) +{ + gchar *slice; + JsonNode *root; + JsonParser *prsr; + + g_return_val_if_fail(data != NULL, NULL); + + if (size < 0) { + size = strlen(data); + } + + /* Ensure data is null terminated for json-glib < 1.0.2 */ + slice = g_strndup(data, size); + prsr = json_parser_new(); + + if (!json_parser_load_from_data(prsr, slice, size, error)) { + g_object_unref(prsr); + g_free(slice); + return NULL; + } + + root = json_parser_get_root(prsr); + root = json_node_copy(root); + + g_object_unref(prsr); + g_free(slice); + return root; +} + +JsonNode * +fb_json_node_get(JsonNode *root, const gchar *expr, GError **error) +{ + GError *err = NULL; + guint size; + JsonArray *rslt; + JsonNode *node; + JsonNode *ret; + + /* Special case for json-glib < 0.99.2 */ + if (purple_strequal(expr, "$")) { + return json_node_copy(root); + } + + node = json_path_query(expr, root, &err); + + if (err != NULL) { + g_propagate_error(error, err); + json_node_free(node); + return NULL; + } + + rslt = json_node_get_array(node); + size = json_array_get_length(rslt); + + if (size < 1) { + g_set_error(error, FB_JSON_ERROR, FB_JSON_ERROR_NOMATCH, + _("No matches for %s"), expr); + json_node_free(node); + return NULL; + } + + if (size > 1) { + g_set_error(error, FB_JSON_ERROR, FB_JSON_ERROR_AMBIGUOUS, + _("Ambiguous matches for %s"), expr); + json_node_free(node); + return NULL; + } + + if (json_array_get_null_element(rslt, 0)) { + g_set_error(error, FB_JSON_ERROR, FB_JSON_ERROR_NULL, + _("Null value for %s"), expr); + json_node_free(node); + return NULL; + } + + ret = json_array_dup_element(rslt, 0); + json_node_free(node); + return ret; +} + +JsonNode * +fb_json_node_get_nth(JsonNode *root, guint n) +{ + GList *vals; + JsonNode *ret; + JsonObject *obj; + + obj = json_node_get_object(root); + vals = json_object_get_values(obj); + ret = g_list_nth_data(vals, n); + + g_list_free(vals); + return ret; +} + +JsonArray * +fb_json_node_get_arr(JsonNode *root, const gchar *expr, GError **error) +{ + JsonArray *ret; + JsonNode *rslt; + + rslt = fb_json_node_get(root, expr, error); + + if (rslt == NULL) { + return NULL; + } + + ret = json_node_dup_array(rslt); + json_node_free(rslt); + return ret; +} + +gboolean +fb_json_node_get_bool(JsonNode *root, const gchar *expr, GError **error) +{ + gboolean ret; + JsonNode *rslt; + + rslt = fb_json_node_get(root, expr, error); + + if (rslt == NULL) { + return FALSE; + } + + ret = json_node_get_boolean(rslt); + json_node_free(rslt); + return ret; +} + +gdouble +fb_json_node_get_dbl(JsonNode *root, const gchar *expr, GError **error) +{ + gdouble ret; + JsonNode *rslt; + + rslt = fb_json_node_get(root, expr, error); + + if (rslt == NULL) { + return 0.0; + } + + ret = json_node_get_double(rslt); + json_node_free(rslt); + return ret; +} + +gint64 +fb_json_node_get_int(JsonNode *root, const gchar *expr, GError **error) +{ + gint64 ret; + JsonNode *rslt; + + rslt = fb_json_node_get(root, expr, error); + + if (rslt == NULL) { + return 0; + } + + ret = json_node_get_int(rslt); + json_node_free(rslt); + return ret; +} + +gchar * +fb_json_node_get_str(JsonNode *root, const gchar *expr, GError **error) +{ + gchar *ret; + JsonNode *rslt; + + rslt = fb_json_node_get(root, expr, error); + + if (rslt == NULL) { + return NULL; + } + + ret = json_node_dup_string(rslt); + json_node_free(rslt); + return ret; +} + +FbJsonValues * +fb_json_values_new(JsonNode *root) +{ + FbJsonValues *values; + FbJsonValuesPrivate *priv; + + g_return_val_if_fail(root != NULL, NULL); + + values = g_object_new(FB_TYPE_JSON_VALUES, NULL); + priv = values->priv; + priv->root = root; + + return values; +} + +void +fb_json_values_add(FbJsonValues *values, FbJsonType type, gboolean required, + const gchar *expr) +{ + FbJsonValue *value; + FbJsonValuesPrivate *priv; + + g_return_if_fail(values != NULL); + g_return_if_fail(expr != NULL); + priv = values->priv; + + value = g_new0(FbJsonValue, 1); + value->expr = expr; + value->type = type; + value->required = required; + + g_queue_push_tail(priv->queue, value); +} + +JsonNode * +fb_json_values_get_root(FbJsonValues *values) +{ + FbJsonValuesPrivate *priv; + guint index; + + g_return_val_if_fail(values != NULL, NULL); + priv = values->priv; + + if (priv->array == NULL) { + return priv->root; + } + + g_return_val_if_fail(priv->index > 0, NULL); + index = priv->index - 1; + + if (json_array_get_length(priv->array) <= index) { + return NULL; + } + + return json_array_get_element(priv->array, index); +} + +void +fb_json_values_set_array(FbJsonValues *values, gboolean required, + const gchar *expr) +{ + FbJsonValuesPrivate *priv; + + g_return_if_fail(values != NULL); + priv = values->priv; + + priv->array = fb_json_node_get_arr(priv->root, expr, &priv->error); + priv->isarray = TRUE; + + if ((priv->error != NULL) && !required) { + g_clear_error(&priv->error); + } +} + +gboolean +fb_json_values_update(FbJsonValues *values, GError **error) +{ + FbJsonValue *value; + FbJsonValuesPrivate *priv; + GError *err = NULL; + GList *l; + GType type; + JsonNode *root; + JsonNode *node; + + g_return_val_if_fail(values != NULL, FALSE); + priv = values->priv; + + if (G_UNLIKELY(priv->error != NULL)) { + g_propagate_error(error, priv->error); + priv->error = NULL; + return FALSE; + } + + if (priv->isarray) { + if ((priv->array == NULL) || + (json_array_get_length(priv->array) <= priv->index)) + { + return FALSE; + } + + root = json_array_get_element(priv->array, priv->index++); + } else { + root = priv->root; + } + + g_return_val_if_fail(root != NULL, FALSE); + + for (l = priv->queue->head; l != NULL; l = l->next) { + value = l->data; + node = fb_json_node_get(root, value->expr, &err); + + if (G_IS_VALUE(&value->value)) { + g_value_unset(&value->value); + } + + if (err != NULL) { + json_node_free(node); + + if (value->required) { + g_propagate_error(error, err); + return FALSE; + } + + g_clear_error(&err); + continue; + } + + type = json_node_get_value_type(node); + + if (G_UNLIKELY(type != value->type)) { + g_set_error(error, FB_JSON_ERROR, FB_JSON_ERROR_TYPE, + _("Expected a %s but got a %s for %s"), + g_type_name(value->type), + g_type_name(type), + value->expr); + json_node_free(node); + return FALSE; + } + + json_node_get_value(node, &value->value); + json_node_free(node); + } + + priv->next = priv->queue->head; + return TRUE; +} + +const GValue * +fb_json_values_next(FbJsonValues *values) +{ + FbJsonValue *value; + FbJsonValuesPrivate *priv; + + g_return_val_if_fail(values != NULL, NULL); + priv = values->priv; + + g_return_val_if_fail(priv->next != NULL, NULL); + value = priv->next->data; + priv->next = priv->next->next; + + if (!G_IS_VALUE(&value->value)) { + return NULL; + } + + return &value->value; +} + +gboolean +fb_json_values_next_bool(FbJsonValues *values, gboolean defval) +{ + const GValue *value; + + value = fb_json_values_next(values); + + if (G_UNLIKELY(value == NULL)) { + return defval; + } + + return g_value_get_boolean(value); +} + +gdouble +fb_json_values_next_dbl(FbJsonValues *values, gdouble defval) +{ + const GValue *value; + + value = fb_json_values_next(values); + + if (G_UNLIKELY(value == NULL)) { + return defval; + } + + return g_value_get_double(value); +} + +gint64 +fb_json_values_next_int(FbJsonValues *values, gint64 defval) +{ + const GValue *value; + + value = fb_json_values_next(values); + + if (G_UNLIKELY(value == NULL)) { + return defval; + } + + return g_value_get_int64(value); +} + +const gchar * +fb_json_values_next_str(FbJsonValues *values, const gchar *defval) +{ + const GValue *value; + + value = fb_json_values_next(values); + + if (G_UNLIKELY(value == NULL)) { + return defval; + } + + return g_value_get_string(value); +} + +gchar * +fb_json_values_next_str_dup(FbJsonValues *values, const gchar *defval) +{ + const GValue *value; + + value = fb_json_values_next(values); + + if (G_UNLIKELY(value == NULL)) { + return g_strdup(defval); + } + + return g_value_dup_string(value); +} diff --git a/pidgin/libpurple/protocols/facebook/json.h b/pidgin/libpurple/protocols/facebook/json.h new file mode 100644 index 00000000..2a462163 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/json.h @@ -0,0 +1,517 @@ +/* purple + * + * 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 + */ + +#ifndef _FACEBOOK_JSON_H_ +#define _FACEBOOK_JSON_H_ + +/** + * SECTION:json + * @section_id: facebook-json + * @short_description: json.h + * @title: JSON Utilities + * + * The JSON utilities. + */ + +#include +#include + +#define FB_TYPE_JSON_VALUES (fb_json_values_get_type()) +#define FB_JSON_VALUES(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_JSON_VALUES, FbJsonValues)) +#define FB_JSON_VALUES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_JSON_VALUES, FbJsonValuesClass)) +#define FB_IS_JSON_VALUES(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_JSON_VALUES)) +#define FB_IS_JSON_VALUES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_JSON_VALUES)) +#define FB_JSON_VALUES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_JSON_VALUES, FbJsonValuesClass)) + +/** + * FB_JSON_ERROR: + * + * The #GQuark of the domain of JSON errors. + */ +#define FB_JSON_ERROR fb_json_error_quark() + +typedef struct _FbJsonValues FbJsonValues; +typedef struct _FbJsonValuesClass FbJsonValuesClass; +typedef struct _FbJsonValuesPrivate FbJsonValuesPrivate; + +/** + * FbJsonError: + * @FB_JSON_ERROR_SUCCESS: There is no error. + * @FB_JSON_ERROR_AMBIGUOUS: The node has ambiguous matches. + * @FB_JSON_ERROR_GENERAL: General failure. + * @FB_JSON_ERROR_NOMATCH: The node does not match anything. + * @FB_JSON_ERROR_NULL: The node is of type NULL. + * @FB_JSON_ERROR_TYPE: The node has an unexpected type. + * + * The error codes for the #FB_JSON_ERROR domain. + */ +typedef enum +{ + FB_JSON_ERROR_SUCCESS = 0, + FB_JSON_ERROR_AMBIGUOUS, + FB_JSON_ERROR_GENERAL, + FB_JSON_ERROR_NOMATCH, + FB_JSON_ERROR_NULL, + FB_JSON_ERROR_TYPE +} FbJsonError; + +/** + * FbJsonType: + * @FB_JSON_TYPE_NULL: An unknown value. + * @FB_JSON_TYPE_BOOL: A boolean (#TRUE or #FALSE). + * @FB_JSON_TYPE_DBL: A floating point number. + * @FB_JSON_TYPE_INT: A signed integer. + * @FB_JSON_TYPE_STR: A string. + * + * The JSON data types. + */ +typedef enum +{ + FB_JSON_TYPE_NULL = 0, + FB_JSON_TYPE_BOOL = G_TYPE_BOOLEAN, + FB_JSON_TYPE_DBL = G_TYPE_DOUBLE, + FB_JSON_TYPE_INT = G_TYPE_INT64, + FB_JSON_TYPE_STR = G_TYPE_STRING +} FbJsonType; + +/** + * FbJsonValues: + * + * Represents a JSON value handler. + */ +struct _FbJsonValues +{ + /*< private >*/ + GObject parent; + FbJsonValuesPrivate *priv; +}; + +/** + * FbJsonValuesClass: + * + * The base class for all #FbJsonValues's. + */ +struct _FbJsonValuesClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +/** + * fb_json_values_get_type: + * + * Returns: The #GType for an #FbJsonValues. + */ +GType +fb_json_values_get_type(void); + +/** + * fb_json_error_quark: + * + * Gets the #GQuark of the domain of JSON errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_json_error_quark(void); + +/** + * fb_json_bldr_new: + * @type: The starting #JsonNodeType. + * + * Creates a new #JsonBuilder. The starting #JsonNodeType is likely to + * be #JSON_NODE_OBJECT. The returned #JsonBuilder should be freed with + * #g_object_unref() when no longer needed. Optionally, instead of + * freeing, the returned #JsonBuilder can be closed with + * #fb_json_bldr_close(). + * + * Returns: The new #JsonBuilder. + */ +JsonBuilder * +fb_json_bldr_new(JsonNodeType type); + +/** + * fb_json_bldr_close: + * @bldr: The #JsonBuilder. + * @type: The ending #JsonNodeType. + * @size: The return local for the size of the returned string. + * + * Closes the #JsonBuilder by returning a string representing the + * #JsonBuilder. The ending #JsonNodeType is likely to be + * #JSON_NODE_OBJECT. This calls #g_object_unref(). The returned + * string should be freed with #g_free() when no longer needed. + * + * Returns: The string representation of the #JsonBuilder. + */ +gchar * +fb_json_bldr_close(JsonBuilder *bldr, JsonNodeType type, gsize *size); + +/** + * fb_json_bldr_arr_begin: + * @bldr: The #JsonBuilder. + * @name: The member name or #NULL. + * + * Begins an array member in the #JsonBuilder. + */ +void +fb_json_bldr_arr_begin(JsonBuilder *bldr, const gchar *name); + +/** + * fb_json_bldr_arr_end: + * @bldr: The #JsonBuilder. + * + * Ends an array member in the #JsonBuilder. + */ +void +fb_json_bldr_arr_end(JsonBuilder *bldr); + +/** + * fb_json_bldr_obj_begin: + * @bldr: The #JsonBuilder. + * @name: The member name or #NULL. + * + * Begins an object member in the #JsonBuilder. + */ +void +fb_json_bldr_obj_begin(JsonBuilder *bldr, const gchar *name); + +/** + * fb_json_bldr_obj_end: + * @bldr: The #JsonBuilder. + * + * Ends an array member in the #JsonBuilder. + */ +void +fb_json_bldr_obj_end(JsonBuilder *bldr); + +/** + * fb_json_bldr_add_bool: + * @bldr: The #JsonBuilder. + * @name: The member name or #NULL. + * @value: The value. + * + * Adds a boolean memeber to the #JsonBuilder. + */ +void +fb_json_bldr_add_bool(JsonBuilder *bldr, const gchar *name, gboolean value); + +/** + * fb_json_bldr_add_dbl: + * @bldr: The #JsonBuilder. + * @name: The member name or #NULL. + * @value: The value. + * + * Adds a floating point memeber to the #JsonBuilder. + */ +void +fb_json_bldr_add_dbl(JsonBuilder *bldr, const gchar *name, gdouble value); + +/** + * fb_json_bldr_add_int: + * @bldr: The #JsonBuilder. + * @name: The member name or #NULL. + * @value: The value. + * + * Adds a integer memeber to the #JsonBuilder. + */ +void +fb_json_bldr_add_int(JsonBuilder *bldr, const gchar *name, gint64 value); + +/** + * fb_json_bldr_add_str: + * @bldr: The #JsonBuilder. + * @name: The member name or #NULL. + * @value: The value. + * + * Adds a string memeber to the #JsonBuilder. + */ +void +fb_json_bldr_add_str(JsonBuilder *bldr, const gchar *name, const gchar *value); + +/** + * fb_json_bldr_add_strf: + * @bldr: The #JsonBuilder. + * @name: The member name or #NULL. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Adds a formatted string memeber to the #JsonBuilder. + */ +void +fb_json_bldr_add_strf(JsonBuilder *bldr, const gchar *name, + const gchar *format, ...) + G_GNUC_PRINTF(3, 4); + +/** + * fb_json_node_new: + * @data: The string JSON. + * @size: The size of @json or -1 if null-terminated. + * @error: The return location for the #GError or #NULL. + * + * Creates a new #JsonNode. The returned #JsonBuilder should be freed + * wuth #json_node_free() when no longer needed. + * + * Returns: The new #JsonNode. + */ +JsonNode * +fb_json_node_new(const gchar *data, gssize size, GError **error); + +/** + * fb_json_node_get: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError or #NULL. + * + * Gets a new #JsonNode value from a parent #JsonNode with a #JsonPath + * expression. The returned #JsonNode should be freed with + * #json_node_free() when no longer needed. + * + * Returns: The new #JsonNode. + */ +JsonNode * +fb_json_node_get(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_node_get_nth: + * @root: The root #JsonNode. + * @n: The index number. + * + * Gets a #JsonNode value from a parent #JsonNode by index. The + * returned #JsonNode should not be freed. + * + * Return: The #JsonNode. + */ +JsonNode * +fb_json_node_get_nth(JsonNode *root, guint n); + +/** + * fb_json_node_get_arr: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError or #NULL. + * + * Gets a new #JsonArray value from a parent #JsonNode with a #JsonPath + * expression. The returned #JsonArray should be freed with + * #json_array_unref() when no longer needed. + * + * Returns: The new #JsonArray. + */ +JsonArray * +fb_json_node_get_arr(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_node_get_bool: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError or #NULL. + * + * Gets a boolean value from a parent #JsonNode with a #JsonPath + * expression. + * + * Returns: The boolean value. + */ +gboolean +fb_json_node_get_bool(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_node_get_dbl: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError or #NULL. + * + * Gets a floating point value from a parent #JsonNode with a #JsonPath + * expression. + * + * Returns: The floating point value. + */ +gdouble +fb_json_node_get_dbl(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_node_get_int: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError or #NULL. + * + * Gets an integer value from a parent #JsonNode with a #JsonPath + * expression. + * + * Returns: The integer value. + */ +gint64 +fb_json_node_get_int(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_node_get_str: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError or #NULL. + * + * Gets an string value from a parent #JsonNode with a #JsonPath + * expression. The returned string should be freed with #g_free() + * when no longer needed. + * + * Returns: The string value. + */ +gchar * +fb_json_node_get_str(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_values_new: + * @root: The root #JsonNode. + * + * Creates a new #FbJsonValues. The returned #FbJsonValues should be + * freed with #g_object_unref when no longer needed. + * + * Returns: The new #FbJsonValues. + */ +FbJsonValues * +fb_json_values_new(JsonNode *root); + +/** + * fb_json_values_add: + * @values: The #FbJsonValues. + * @type: The #FbJsonType. + * @required: #TRUE if the node is required, otherwise #FALSE. + * @expr: The #JsonPath expression. + * + * Adds a new #FbJsonValue to the #FbJsonValues. + */ +void +fb_json_values_add(FbJsonValues *values, FbJsonType type, gboolean required, + const gchar *expr); + +/** + * fb_json_values_get_root: + * @values: The #FbJsonValues. + * + * Gets the current working root #JsonNode. This is either the current + * array #JsonNode or the root #JsonNode. The returned #JsonNode should + * not be freed. + */ +JsonNode * +fb_json_values_get_root(FbJsonValues *values); + +/** + * fb_json_values_set_array: + * @values: The #FbJsonValues. + * @required: #TRUE if the node is required, otherwise #FALSE. + * @expr: The #JsonPath expression. + * + * Sets the #JsonPath for an array to base all #FbJsonValue's off. + */ +void +fb_json_values_set_array(FbJsonValues *values, gboolean required, + const gchar *expr); + +/** + * fb_json_values_update: + * @values: The #FbJsonValues. + * @error: The return location for the #GError or #NULL. + * + * Updates the current working root. This should be called after all of + * the #FbJsonValue's have been added with #fb_json_values_add(). If an + * array was set with #fb_json_values_set_array(), then this should be + * called in a while loop, until #FALSE is returned. + * + * Returns: #TRUE if the values were updated, otherwise #FALSE. + */ +gboolean +fb_json_values_update(FbJsonValues *values, GError **error); + +/** + * fb_json_values_next: + * @values: The #FbJsonValues. + * + * Gets the next #GValue from the #FbJsonValues. Before calling this + * function, #fb_json_values_update() must be called. + * + * Returns: The #GValue. + */ +const GValue * +fb_json_values_next(FbJsonValues *values); + +/** + * fb_json_values_next_bool: + * @values: The #FbJsonValues. + * @defval: The default value. + * + * Gets the next boolean value from the #FbJsonValues. Before calling + * this function, #fb_json_values_update() must be called. + * + * Returns: The boolean value. + */ +gboolean +fb_json_values_next_bool(FbJsonValues *values, gboolean defval); + +/** + * fb_json_values_next_dbl: + * @values: The #FbJsonValues. + * @defval: The default value. + * + * Gets the next floating point value from the #FbJsonValues. Before + * calling this function, #fb_json_values_update() must be called. + * + * Returns: The floating point value. + */ +gdouble +fb_json_values_next_dbl(FbJsonValues *values, gdouble defval); + +/** + * fb_json_values_next_int: + * @values: The #FbJsonValues. + * @defval: The default value. + * + * Gets the next integer value from the #FbJsonValues. Before calling + * this function, #fb_json_values_update() must be called. + * + * Returns: The integer value. + */ +gint64 +fb_json_values_next_int(FbJsonValues *values, gint64 defval); + +/** + * fb_json_values_next_str: + * @values: The #FbJsonValues. + * @defval: The default value. + * + * Gets the next string value from the #FbJsonValues. Before calling + * this function, #fb_json_values_update() must be called. + * + * Returns: The string value. + */ +const gchar * +fb_json_values_next_str(FbJsonValues *values, const gchar *defval); + +/** + * fb_json_values_next_str_dup: + * @values: The #FbJsonValues. + * @defval: The default value. + * + * Gets the next duplicate string value from the #FbJsonValues. Before + * calling this function, #fb_json_values_update() must be called. + * + * Returns: The duplicate string value. + */ +gchar * +fb_json_values_next_str_dup(FbJsonValues *values, const gchar *defval); + +#endif /* _FACEBOOK_JSON_H_ */ diff --git a/pidgin/libpurple/protocols/facebook/marshal.c b/pidgin/libpurple/protocols/facebook/marshal.c new file mode 100644 index 00000000..65fe2bca --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/marshal.c @@ -0,0 +1,195 @@ +/* This file is generated by glib-genmarshal, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */ +#include + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_schar (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#define g_marshal_value_peek_variant(v) g_value_get_variant (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + +/* VOID:INT64 (pidgin/libpurple/protocols/facebook/marshaller.list:1) */ +void +fb_marshal_VOID__INT64 (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__INT64) (gpointer data1, + gint64 arg1, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__INT64 callback; + + g_return_if_fail (n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__INT64) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_int64 (param_values + 1), + data2); +} + +/* VOID:POINTER,BOOLEAN (pidgin/libpurple/protocols/facebook/marshaller.list:4) */ +void +fb_marshal_VOID__POINTER_BOOLEAN (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__POINTER_BOOLEAN) (gpointer data1, + gpointer arg1, + gboolean arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__POINTER_BOOLEAN callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__POINTER_BOOLEAN) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_boolean (param_values + 2), + data2); +} + +/* VOID:STRING,BOXED (pidgin/libpurple/protocols/facebook/marshaller.list:5) */ +void +fb_marshal_VOID__STRING_BOXED (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__STRING_BOXED) (gpointer data1, + gpointer arg1, + gpointer arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__STRING_BOXED callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__STRING_BOXED) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_string (param_values + 1), + g_marshal_value_peek_boxed (param_values + 2), + data2); +} + +/* VOID:POINTER,POINTER (pidgin/libpurple/protocols/facebook/marshaller.list:7) */ +void +fb_marshal_VOID__POINTER_POINTER (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__POINTER_POINTER) (gpointer data1, + gpointer arg1, + gpointer arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__POINTER_POINTER callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__POINTER_POINTER) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_pointer (param_values + 2), + data2); +} + diff --git a/pidgin/libpurple/protocols/facebook/marshal.h b/pidgin/libpurple/protocols/facebook/marshal.h new file mode 100644 index 00000000..487dd3f0 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/marshal.h @@ -0,0 +1,57 @@ +/* This file is generated by glib-genmarshal, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */ +#ifndef __FB_MARSHAL_MARSHAL_H__ +#define __FB_MARSHAL_MARSHAL_H__ + +#include + +G_BEGIN_DECLS + +/* VOID:INT64 (pidgin/libpurple/protocols/facebook/marshaller.list:1) */ +extern +void fb_marshal_VOID__INT64 (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID:OBJECT (pidgin/libpurple/protocols/facebook/marshaller.list:2) */ +#define fb_marshal_VOID__OBJECT g_cclosure_marshal_VOID__OBJECT + +/* VOID:POINTER (pidgin/libpurple/protocols/facebook/marshaller.list:3) */ +#define fb_marshal_VOID__POINTER g_cclosure_marshal_VOID__POINTER + +/* VOID:POINTER,BOOLEAN (pidgin/libpurple/protocols/facebook/marshaller.list:4) */ +extern +void fb_marshal_VOID__POINTER_BOOLEAN (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID:STRING,BOXED (pidgin/libpurple/protocols/facebook/marshaller.list:5) */ +extern +void fb_marshal_VOID__STRING_BOXED (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID:VOID (pidgin/libpurple/protocols/facebook/marshaller.list:6) */ +#define fb_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID + +/* VOID:POINTER,POINTER (pidgin/libpurple/protocols/facebook/marshaller.list:7) */ +extern +void fb_marshal_VOID__POINTER_POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + + +G_END_DECLS + +#endif /* __FB_MARSHAL_MARSHAL_H__ */ diff --git a/pidgin/libpurple/protocols/facebook/marshaller.list b/pidgin/libpurple/protocols/facebook/marshaller.list new file mode 100644 index 00000000..ef5d4c9a --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/marshaller.list @@ -0,0 +1,7 @@ +VOID:INT64 +VOID:OBJECT +VOID:POINTER +VOID:POINTER,BOOLEAN +VOID:STRING,BOXED +VOID:VOID +VOID:POINTER,POINTER diff --git a/pidgin/libpurple/protocols/facebook/mqtt.c b/pidgin/libpurple/protocols/facebook/mqtt.c new file mode 100644 index 00000000..ec87b0f7 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/mqtt.c @@ -0,0 +1,1023 @@ +/* purple + * + * 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 +#include +#include + +#include "account.h" +#include "eventloop.h" +#include "glibcompat.h" +#include "sslconn.h" + +#include "marshal.h" +#include "mqtt.h" +#include "util.h" + +struct _FbMqttPrivate +{ + PurpleConnection *gc; + PurpleSslConnection *gsc; + gboolean connected; + guint16 mid; + + GByteArray *rbuf; + GByteArray *wbuf; + gsize remz; + + gint tev; + gint rev; + gint wev; +}; + +struct _FbMqttMessagePrivate +{ + FbMqttMessageType type; + FbMqttMessageFlags flags; + + GByteArray *bytes; + guint offset; + guint pos; + + gboolean local; +}; + +G_DEFINE_TYPE_WITH_CODE(FbMqtt, fb_mqtt, G_TYPE_OBJECT, G_ADD_PRIVATE(FbMqtt)); +G_DEFINE_TYPE_WITH_CODE(FbMqttMessage, fb_mqtt_message, G_TYPE_OBJECT, G_ADD_PRIVATE(FbMqttMessage)); + +static void +fb_mqtt_dispose(GObject *obj) +{ + FbMqtt *mqtt = FB_MQTT(obj); + FbMqttPrivate *priv = mqtt->priv; + + fb_mqtt_close(mqtt); + g_byte_array_free(priv->rbuf, TRUE); + g_byte_array_free(priv->wbuf, TRUE); +} + +static void +fb_mqtt_class_init(FbMqttClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); + + gklass->dispose = fb_mqtt_dispose; + g_type_class_add_private(klass, sizeof (FbMqttPrivate)); + + /** + * FbMqtt::connect: + * @mqtt: The #FbMqtt. + * + * Emitted upon the successful completion of the connection + * process. This is emitted as a result of #fb_mqtt_connect(). + */ + g_signal_new("connect", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * FbMqtt::error: + * @mqtt: The #FbMqtt. + * @error: The #GError. + * + * Emitted whenever an error is hit within the #FbMqtt. This + * should close the #FbMqtt with #fb_mqtt_close(). + */ + g_signal_new("error", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbMqtt::open: + * @mqtt: The #FbMqtt. + * + * Emitted upon the successful opening of the remote socket. + * This is emitted as a result of #fb_mqtt_open(). This should + * call #fb_mqtt_connect(). + */ + g_signal_new("open", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * FbMqtt::publish: + * @mqtt: The #FbMqtt. + * @topic: The topic. + * @pload: The payload. + * + * Emitted upon an incoming message from the steam. + */ + g_signal_new("publish", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__STRING_BOXED, + G_TYPE_NONE, + 2, G_TYPE_STRING, G_TYPE_BYTE_ARRAY); +} + +static void +fb_mqtt_init(FbMqtt *mqtt) +{ + FbMqttPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(mqtt, FB_TYPE_MQTT, FbMqttPrivate); + mqtt->priv = priv; + + priv->rbuf = g_byte_array_new(); + priv->wbuf = g_byte_array_new(); +} + +static void +fb_mqtt_message_dispose(GObject *obj) +{ + FbMqttMessagePrivate *priv = FB_MQTT_MESSAGE(obj)->priv; + + if ((priv->bytes != NULL) && priv->local) { + g_byte_array_free(priv->bytes, TRUE); + } +} + +static void +fb_mqtt_message_class_init(FbMqttMessageClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); + + gklass->dispose = fb_mqtt_message_dispose; + g_type_class_add_private(klass, sizeof (FbMqttMessagePrivate)); +} + +static void +fb_mqtt_message_init(FbMqttMessage *msg) +{ + FbMqttMessagePrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(msg, FB_TYPE_MQTT_MESSAGE, + FbMqttMessagePrivate); + msg->priv = priv; +} + +GQuark +fb_mqtt_error_quark(void) +{ + static GQuark q = 0; + + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-mqtt-error-quark"); + } + + return q; +} + +GQuark +fb_mqtt_ssl_error_quark(void) +{ + static GQuark q = 0; + + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-mqtt-ssl-error-quark"); + } + + return q; +} + +FbMqtt * +fb_mqtt_new(PurpleConnection *gc) +{ + FbMqtt *mqtt; + FbMqttPrivate *priv; + + g_return_val_if_fail(PURPLE_IS_CONNECTION(gc), NULL); + + mqtt = g_object_new(FB_TYPE_MQTT, NULL); + priv = mqtt->priv; + priv->gc = gc; + + return mqtt; +}; + +void +fb_mqtt_close(FbMqtt *mqtt) +{ + FbMqttPrivate *priv; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + priv = mqtt->priv; + + if (priv->wev > 0) { + purple_input_remove(priv->wev); + priv->wev = 0; + } + + if (priv->rev > 0) { + purple_input_remove(priv->rev); + priv->rev = 0; + } + + if (priv->tev > 0) { + purple_timeout_remove(priv->tev); + priv->tev = 0; + } + + if (priv->gsc != NULL) { + purple_ssl_close(priv->gsc); + priv->gsc = NULL; + } + + if (priv->wbuf->len > 0) { + fb_util_debug_warning("Closing with unwritten data"); + } + + priv->connected = FALSE; + g_byte_array_set_size(priv->rbuf, 0); + g_byte_array_set_size(priv->wbuf, 0); +} + +void +fb_mqtt_error(FbMqtt *mqtt, FbMqttError error, const gchar *format, ...) +{ + GError *err; + va_list ap; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + + va_start(ap, format); + err = g_error_new_valist(FB_MQTT_ERROR, error, format, ap); + va_end(ap); + + g_signal_emit_by_name(mqtt, "error", err); + g_error_free(err); +} + +static gboolean +fb_mqtt_cb_timeout(gpointer data) +{ + FbMqtt *mqtt = data; + FbMqttPrivate *priv = mqtt->priv; + + priv->tev = 0; + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, _("Connection timed out")); + return FALSE; +} + +static void +fb_mqtt_timeout_clear(FbMqtt *mqtt) +{ + FbMqttPrivate *priv = mqtt->priv; + + if (priv->tev > 0) { + g_source_remove(priv->tev); + priv->tev = 0; + } +} + +static void +fb_mqtt_timeout(FbMqtt *mqtt) +{ + FbMqttPrivate *priv = mqtt->priv; + + fb_mqtt_timeout_clear(mqtt); + priv->tev = g_timeout_add(FB_MQTT_TIMEOUT_CONN, + fb_mqtt_cb_timeout, mqtt); +} + +static gboolean +fb_mqtt_cb_ping(gpointer data) +{ + FbMqtt *mqtt = data; + FbMqttMessage *msg; + FbMqttPrivate *priv = mqtt->priv; + + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_PINGREQ, 0); + fb_mqtt_write(mqtt, msg); + g_object_unref(msg); + + priv->tev = 0; + fb_mqtt_timeout(mqtt); + return FALSE; +} + +static void +fb_mqtt_ping(FbMqtt *mqtt) +{ + FbMqttPrivate *priv = mqtt->priv; + + fb_mqtt_timeout_clear(mqtt); + priv->tev = g_timeout_add(FB_MQTT_TIMEOUT_PING, + fb_mqtt_cb_ping, mqtt); +} + +static void +fb_mqtt_cb_read(gpointer data, gint fd, PurpleInputCondition cond) +{ + FbMqtt *mqtt = data; + FbMqttMessage *msg; + FbMqttPrivate *priv = mqtt->priv; + gint res; + guint mult; + guint8 buf[1024]; + guint8 byte; + gsize size; + gssize rize; + + if (priv->remz < 1) { + /* Reset the read buffer */ + g_byte_array_set_size(priv->rbuf, 0); + + res = purple_ssl_read(priv->gsc, &byte, sizeof byte); + + if (res < 0 && errno == EAGAIN) { + return; + } else if (res != 1) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + _("Failed to read fixed header")); + return; + } + + g_byte_array_append(priv->rbuf, &byte, sizeof byte); + + mult = 1; + + do { + res = purple_ssl_read(priv->gsc, &byte, sizeof byte); + + /* TODO: this case isn't handled yet */ + if (0 && res < 0 && errno == EAGAIN) { + return; + } else if (res != 1) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + _("Failed to read packet size")); + return; + } + + g_byte_array_append(priv->rbuf, &byte, sizeof byte); + + priv->remz += (byte & 127) * mult; + mult *= 128; + } while ((byte & 128) != 0); + } + + if (priv->remz > 0) { + size = MIN(priv->remz, sizeof buf); + rize = purple_ssl_read(priv->gsc, buf, size); + + if (rize < 0 && errno == EAGAIN) { + return; + } else if (rize < 1) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + _("Failed to read packet data")); + return; + } + + g_byte_array_append(priv->rbuf, buf, rize); + priv->remz -= rize; + } + + if (priv->remz < 1) { + msg = fb_mqtt_message_new_bytes(priv->rbuf); + priv->remz = 0; + + if (G_UNLIKELY(msg == NULL)) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + _("Failed to parse message")); + return; + } + + fb_mqtt_read(mqtt, msg); + g_object_unref(msg); + } +} + +void +fb_mqtt_read(FbMqtt *mqtt, FbMqttMessage *msg) +{ + FbMqttMessage *nsg; + FbMqttPrivate *priv; + FbMqttMessagePrivate *mriv; + GByteArray *wytes; + gchar *str; + guint8 chr; + guint16 mid; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + g_return_if_fail(FB_IS_MQTT_MESSAGE(msg)); + priv = mqtt->priv; + mriv = msg->priv; + + fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, mriv->bytes, + "Reading %d (flags: 0x%0X)", + mriv->type, mriv->flags); + + switch (mriv->type) { + case FB_MQTT_MESSAGE_TYPE_CONNACK: + if (!fb_mqtt_message_read_byte(msg, NULL) || + !fb_mqtt_message_read_byte(msg, &chr)) + { + break; + } + + if (chr != FB_MQTT_ERROR_SUCCESS) { + fb_mqtt_error(mqtt, chr, _("Connection failed (%u)"), + chr); + return; + } + + priv->connected = TRUE; + fb_mqtt_ping(mqtt); + g_signal_emit_by_name(mqtt, "connect"); + return; + + case FB_MQTT_MESSAGE_TYPE_PUBLISH: + if (!fb_mqtt_message_read_str(msg, &str)) { + break; + } + + if ((mriv->flags & FB_MQTT_MESSAGE_FLAG_QOS1) || + (mriv->flags & FB_MQTT_MESSAGE_FLAG_QOS2)) + { + if (mriv->flags & FB_MQTT_MESSAGE_FLAG_QOS1) { + chr = FB_MQTT_MESSAGE_TYPE_PUBACK; + } else { + chr = FB_MQTT_MESSAGE_TYPE_PUBREC; + } + + if (!fb_mqtt_message_read_mid(msg, &mid)) { + g_free(str); + break; + } + + nsg = fb_mqtt_message_new(chr, 0); + fb_mqtt_message_write_u16(nsg, mid); + fb_mqtt_write(mqtt, nsg); + g_object_unref(nsg); + } + + wytes = g_byte_array_new(); + fb_mqtt_message_read_r(msg, wytes); + g_signal_emit_by_name(mqtt, "publish", str, wytes); + g_byte_array_free(wytes, TRUE); + g_free(str); + return; + + case FB_MQTT_MESSAGE_TYPE_PUBREL: + if (!fb_mqtt_message_read_mid(msg, &mid)) { + break; + } + + nsg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_PUBCOMP, 0); + fb_mqtt_message_write_u16(nsg, mid); /* Message identifier */ + fb_mqtt_write(mqtt, nsg); + g_object_unref(nsg); + return; + + case FB_MQTT_MESSAGE_TYPE_PINGRESP: + fb_mqtt_ping(mqtt); + return; + + case FB_MQTT_MESSAGE_TYPE_PUBACK: + case FB_MQTT_MESSAGE_TYPE_PUBCOMP: + case FB_MQTT_MESSAGE_TYPE_SUBACK: + case FB_MQTT_MESSAGE_TYPE_UNSUBACK: + return; + + default: + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + _("Unknown packet (%u)"), mriv->type); + return; + } + + /* Since no case returned, there was a parse error. */ + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + _("Failed to parse message")); +} + +static void +fb_mqtt_cb_write(gpointer data, gint fd, PurpleInputCondition cond) +{ + FbMqtt *mqtt = data; + FbMqttPrivate *priv = mqtt->priv; + gssize wize; + + wize = purple_ssl_write(priv->gsc, priv->wbuf->data, priv->wbuf->len); + + if (wize < 0) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + _("Failed to write data")); + return; + } + + if (wize > 0) { + g_byte_array_remove_range(priv->wbuf, 0, wize); + } + + if (priv->wbuf->len < 1) { + priv->wev = 0; + } +} + +void +fb_mqtt_write(FbMqtt *mqtt, FbMqttMessage *msg) +{ + const GByteArray *bytes; + FbMqttMessagePrivate *mriv; + FbMqttPrivate *priv; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + g_return_if_fail(FB_IS_MQTT_MESSAGE(msg)); + priv = mqtt->priv; + mriv = msg->priv; + + bytes = fb_mqtt_message_bytes(msg); + + if (G_UNLIKELY(bytes == NULL)) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + _("Failed to format data")); + return; + } + + fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, mriv->bytes, + "Writing %d (flags: 0x%0X)", + mriv->type, mriv->flags); + + g_byte_array_append(priv->wbuf, bytes->data, bytes->len); + fb_mqtt_cb_write(mqtt, priv->gsc->fd, PURPLE_INPUT_WRITE); + + if (priv->wev > 0) { + priv->wev = purple_input_add(priv->gsc->fd, + PURPLE_INPUT_WRITE, + fb_mqtt_cb_write, mqtt); + } +} + +static void +fb_mqtt_cb_open(gpointer data, PurpleSslConnection *ssl, + PurpleInputCondition cond) +{ + FbMqtt *mqtt = data; + FbMqttPrivate *priv = mqtt->priv; + + fb_mqtt_timeout_clear(mqtt); + priv->rev = purple_input_add(priv->gsc->fd, PURPLE_INPUT_READ, + fb_mqtt_cb_read, mqtt); + g_signal_emit_by_name(mqtt, "open"); +} + +static void +fb_mqtt_cb_open_error(PurpleSslConnection *ssl, PurpleSslErrorType error, + gpointer data) +{ + const gchar *str; + FbMqtt *mqtt = data; + FbMqttPrivate *priv = mqtt->priv; + GError *err; + + str = purple_ssl_strerror(error); + err = g_error_new_literal(FB_MQTT_SSL_ERROR, error, str); + + /* Do not call purple_ssl_close() from the error_func */ + priv->gsc = NULL; + + g_signal_emit_by_name(mqtt, "error", err); + g_error_free(err); +} + +void +fb_mqtt_open(FbMqtt *mqtt, const gchar *host, gint port) +{ + FbMqttPrivate *priv; + PurpleAccount *acc; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + priv = mqtt->priv; + + acc = purple_connection_get_account(priv->gc); + fb_mqtt_close(mqtt); + priv->gsc = purple_ssl_connect(acc, host, port, fb_mqtt_cb_open, + fb_mqtt_cb_open_error, mqtt); + + if (priv->gsc == NULL) { + fb_mqtt_cb_open_error(NULL, 0, mqtt); + return; + } + + fb_mqtt_timeout(mqtt); +} + +void +fb_mqtt_connect(FbMqtt *mqtt, guint8 flags, const GByteArray *pload) +{ + FbMqttMessage *msg; + + g_return_if_fail(!fb_mqtt_connected(mqtt, FALSE)); + g_return_if_fail(pload != NULL); + + /* Facebook always sends a CONNACK, use QoS1 */ + flags |= FB_MQTT_CONNECT_FLAG_QOS1; + + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_CONNECT, 0); + fb_mqtt_message_write_str(msg, FB_MQTT_NAME); /* Protocol name */ + fb_mqtt_message_write_byte(msg, FB_MQTT_LEVEL); /* Protocol level */ + fb_mqtt_message_write_byte(msg, flags); /* Flags */ + fb_mqtt_message_write_u16(msg, FB_MQTT_KA); /* Keep alive */ + + fb_mqtt_message_write(msg, pload->data, pload->len); + fb_mqtt_write(mqtt, msg); + + fb_mqtt_timeout(mqtt); + g_object_unref(msg); +} + +gboolean +fb_mqtt_connected(FbMqtt *mqtt, gboolean error) +{ + FbMqttPrivate *priv; + gboolean connected; + + g_return_val_if_fail(FB_IS_MQTT(mqtt), FALSE); + priv = mqtt->priv; + connected = (priv->gsc != NULL) && priv->connected; + + if (!connected && error) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + _("Not connected")); + } + + return connected; +} + +void +fb_mqtt_disconnect(FbMqtt *mqtt) +{ + FbMqttMessage *msg; + + if (G_UNLIKELY(!fb_mqtt_connected(mqtt, FALSE))) { + return; + } + + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_DISCONNECT, 0); + fb_mqtt_write(mqtt, msg); + g_object_unref(msg); + fb_mqtt_close(mqtt); +} + +void +fb_mqtt_publish(FbMqtt *mqtt, const gchar *topic, const GByteArray *pload) +{ + FbMqttMessage *msg; + FbMqttPrivate *priv; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + g_return_if_fail(fb_mqtt_connected(mqtt, FALSE)); + priv = mqtt->priv; + + /* Message identifier not required, but for consistency use QoS1 */ + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_PUBLISH, + FB_MQTT_MESSAGE_FLAG_QOS1); + + fb_mqtt_message_write_str(msg, topic); /* Message topic */ + fb_mqtt_message_write_mid(msg, &priv->mid); /* Message identifier */ + + if (pload != NULL) { + fb_mqtt_message_write(msg, pload->data, pload->len); + } + + fb_mqtt_write(mqtt, msg); + g_object_unref(msg); +} + +void +fb_mqtt_subscribe(FbMqtt *mqtt, const gchar *topic1, guint16 qos1, ...) +{ + const gchar *topic; + FbMqttMessage *msg; + FbMqttPrivate *priv; + guint16 qos; + va_list ap; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + g_return_if_fail(fb_mqtt_connected(mqtt, FALSE)); + priv = mqtt->priv; + + /* Facebook requires a message identifier, use QoS1 */ + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_SUBSCRIBE, + FB_MQTT_MESSAGE_FLAG_QOS1); + + fb_mqtt_message_write_mid(msg, &priv->mid); /* Message identifier */ + fb_mqtt_message_write_str(msg, topic1); /* First topics */ + fb_mqtt_message_write_byte(msg, qos1); /* First QoS value */ + + va_start(ap, qos1); + + while ((topic = va_arg(ap, const gchar*)) != NULL) { + qos = va_arg(ap, guint); + fb_mqtt_message_write_str(msg, topic); /* Remaining topics */ + fb_mqtt_message_write_byte(msg, qos); /* Remaining QoS values */ + } + + va_end(ap); + + fb_mqtt_write(mqtt, msg); + g_object_unref(msg); +} + +void +fb_mqtt_unsubscribe(FbMqtt *mqtt, const gchar *topic1, ...) +{ + const gchar *topic; + FbMqttMessage *msg; + FbMqttPrivate *priv; + va_list ap; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + g_return_if_fail(fb_mqtt_connected(mqtt, FALSE)); + priv = mqtt->priv; + + /* Facebook requires a message identifier, use QoS1 */ + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE, + FB_MQTT_MESSAGE_FLAG_QOS1); + + fb_mqtt_message_write_mid(msg, &priv->mid); /* Message identifier */ + fb_mqtt_message_write_str(msg, topic1); /* First topic */ + + va_start(ap, topic1); + + while ((topic = va_arg(ap, const gchar*)) != NULL) { + fb_mqtt_message_write_str(msg, topic); /* Remaining topics */ + } + + va_end(ap); + + fb_mqtt_write(mqtt, msg); + g_object_unref(msg); +} + +FbMqttMessage * +fb_mqtt_message_new(FbMqttMessageType type, FbMqttMessageFlags flags) +{ + FbMqttMessage *msg; + FbMqttMessagePrivate *priv; + + msg = g_object_new(FB_TYPE_MQTT_MESSAGE, NULL); + priv = msg->priv; + + priv->type = type; + priv->flags = flags; + priv->bytes = g_byte_array_new(); + priv->local = TRUE; + + return msg; +} + +FbMqttMessage * +fb_mqtt_message_new_bytes(GByteArray *bytes) +{ + FbMqttMessage *msg; + FbMqttMessagePrivate *priv; + guint8 *byte; + + g_return_val_if_fail(bytes != NULL, NULL); + g_return_val_if_fail(bytes->len >= 2, NULL); + + msg = g_object_new(FB_TYPE_MQTT_MESSAGE, NULL); + priv = msg->priv; + + priv->bytes = bytes; + priv->local = FALSE; + priv->type = (*bytes->data & 0xF0) >> 4; + priv->flags = *bytes->data & 0x0F; + + /* Skip the fixed header */ + for (byte = priv->bytes->data + 1; (*(byte++) & 128) != 0; ); + priv->offset = byte - bytes->data; + priv->pos = priv->offset; + + return msg; +} + +void +fb_mqtt_message_reset(FbMqttMessage *msg) +{ + FbMqttMessagePrivate *priv; + + g_return_if_fail(FB_IS_MQTT_MESSAGE(msg)); + priv = msg->priv; + + if (priv->offset > 0) { + g_byte_array_remove_range(priv->bytes, 0, priv->offset); + priv->offset = 0; + priv->pos = 0; + } +} + +const GByteArray * +fb_mqtt_message_bytes(FbMqttMessage *msg) +{ + FbMqttMessagePrivate *priv; + guint i; + guint8 byte; + guint8 sbuf[4]; + guint32 size; + + g_return_val_if_fail(FB_IS_MQTT_MESSAGE(msg), NULL); + priv = msg->priv; + + i = 0; + size = priv->bytes->len - priv->offset; + + do { + if (G_UNLIKELY(i >= G_N_ELEMENTS(sbuf))) { + return NULL; + } + + byte = size % 128; + size /= 128; + + if (size > 0) { + byte |= 128; + } + + sbuf[i++] = byte; + } while (size > 0); + + fb_mqtt_message_reset(msg); + g_byte_array_prepend(priv->bytes, sbuf, i); + + byte = ((priv->type & 0x0F) << 4) | (priv->flags & 0x0F); + g_byte_array_prepend(priv->bytes, &byte, sizeof byte); + + priv->pos = (i + 1) * (sizeof byte); + return priv->bytes; +} + +gboolean +fb_mqtt_message_read(FbMqttMessage *msg, gpointer data, guint size) +{ + FbMqttMessagePrivate *priv; + + g_return_val_if_fail(FB_IS_MQTT_MESSAGE(msg), FALSE); + priv = msg->priv; + + if ((priv->pos + size) > priv->bytes->len) { + return FALSE; + } + + if ((data != NULL) && (size > 0)) { + memcpy(data, priv->bytes->data + priv->pos, size); + } + + priv->pos += size; + return TRUE; +} + +gboolean +fb_mqtt_message_read_r(FbMqttMessage *msg, GByteArray *bytes) +{ + FbMqttMessagePrivate *priv; + guint size; + + g_return_val_if_fail(FB_IS_MQTT_MESSAGE(msg), FALSE); + priv = msg->priv; + size = priv->bytes->len - priv->pos; + + if (G_LIKELY(size > 0)) { + g_byte_array_append(bytes, priv->bytes->data + priv->pos, + size); + } + + return TRUE; +} + +gboolean +fb_mqtt_message_read_byte(FbMqttMessage *msg, guint8 *value) +{ + return fb_mqtt_message_read(msg, value, sizeof *value); +} + +gboolean +fb_mqtt_message_read_mid(FbMqttMessage *msg, guint16 *value) +{ + return fb_mqtt_message_read_u16(msg, value); +} + +gboolean +fb_mqtt_message_read_u16(FbMqttMessage *msg, guint16 *value) +{ + if (!fb_mqtt_message_read(msg, value, sizeof *value)) { + return FALSE; + } + + if (value != NULL) { + *value = g_ntohs(*value); + } + + return TRUE; +} + +gboolean +fb_mqtt_message_read_str(FbMqttMessage *msg, gchar **value) +{ + guint8 *data; + guint16 size; + + if (!fb_mqtt_message_read_u16(msg, &size)) { + return FALSE; + } + + if (value != NULL) { + data = g_new(guint8, size + 1); + data[size] = 0; + } else { + data = NULL; + } + + if (!fb_mqtt_message_read(msg, data, size)) { + g_free(data); + return FALSE; + } + + if (value != NULL) { + *value = (gchar *) data; + } + + return TRUE; +} + +void +fb_mqtt_message_write(FbMqttMessage *msg, gconstpointer data, guint size) +{ + FbMqttMessagePrivate *priv; + + g_return_if_fail(FB_IS_MQTT_MESSAGE(msg)); + priv = msg->priv; + + g_byte_array_append(priv->bytes, data, size); + priv->pos += size; +} + +void +fb_mqtt_message_write_byte(FbMqttMessage *msg, guint8 value) +{ + fb_mqtt_message_write(msg, &value, sizeof value); +} + +void +fb_mqtt_message_write_mid(FbMqttMessage *msg, guint16 *value) +{ + g_return_if_fail(value != NULL); + fb_mqtt_message_write_u16(msg, ++(*value)); +} + +void +fb_mqtt_message_write_u16(FbMqttMessage *msg, guint16 value) +{ + value = g_htons(value); + fb_mqtt_message_write(msg, &value, sizeof value); +} + +void +fb_mqtt_message_write_str(FbMqttMessage *msg, const gchar *value) +{ + gint16 size; + + g_return_if_fail(value != NULL); + + size = strlen(value); + fb_mqtt_message_write_u16(msg, size); + fb_mqtt_message_write(msg, value, size); +} diff --git a/pidgin/libpurple/protocols/facebook/mqtt.h b/pidgin/libpurple/protocols/facebook/mqtt.h new file mode 100644 index 00000000..a33b4f52 --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/mqtt.h @@ -0,0 +1,626 @@ +/* purple + * + * 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 + */ + +#ifndef _FACEBOOK_MQTT_H_ +#define _FACEBOOK_MQTT_H_ + +/** + * SECTION:mqtt + * @section_id: facebook-mqtt + * @short_description: mqtt.h + * @title: MQTT Connection + * + * The MQTT connection. + */ + +#include +#include + +#include "connection.h" + +#define FB_TYPE_MQTT (fb_mqtt_get_type()) +#define FB_MQTT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_MQTT, FbMqtt)) +#define FB_MQTT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_MQTT, FbMqttClass)) +#define FB_IS_MQTT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_MQTT)) +#define FB_IS_MQTT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_MQTT)) +#define FB_MQTT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_MQTT, FbMqttClass)) + +#define FB_TYPE_MQTT_MESSAGE (fb_mqtt_message_get_type()) +#define FB_MQTT_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_MQTT_MESSAGE, FbMqttMessage)) +#define FB_MQTT_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_MQTT_MESSAGE, FbMqttMessageClass)) +#define FB_IS_MQTT_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_MQTT_MESSAGE)) +#define FB_IS_MQTT_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_MQTT_MESSAGE)) +#define FB_MQTT_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_MQTT_MESSAGE, FbMqttMessageClass)) + +/** + * FB_MQTT_NAME: + * + * The name of the MQTT version. + */ +#define FB_MQTT_NAME "MQTToT" + +/** + * FB_MQTT_LEVEL: + * + * The level of the MQTT version. + */ +#define FB_MQTT_LEVEL 3 + +/** + * FB_MQTT_KA: + * + * The keep-alive timeout, in seconds, of the MQTT connection. + */ +#define FB_MQTT_KA 60 + +/** + * FB_MQTT_HOST: + * + * The MQTT host name for Facebook. + */ +#define FB_MQTT_HOST "mqtt.facebook.com" + +/** + * FB_MQTT_PORT: + * + * The MQTT host port for Facebook. + */ +#define FB_MQTT_PORT 443 + +/** + * FB_MQTT_TIMEOUT_CONN: + * + * The timeout, in milliseconds, to wait for a PING back from the + * server. + */ +#define FB_MQTT_TIMEOUT_CONN (FB_MQTT_KA * 1500) + +/** + * FB_MQTT_TIMEOUT_PING: + * + * The timeout, in milliseconds, to send a PING to the server. + */ +#define FB_MQTT_TIMEOUT_PING (FB_MQTT_KA * 1000) + +/** + * FB_MQTT_ERROR: + * + * The #GQuark of the domain of MQTT errors. + */ +#define FB_MQTT_ERROR fb_mqtt_error_quark() + +/** + * FB_MQTT_SSL_ERROR: + * + * The #GQuark of the domain of MQTT SSL errors. + */ +#define FB_MQTT_SSL_ERROR fb_mqtt_ssl_error_quark() + +typedef struct _FbMqtt FbMqtt; +typedef struct _FbMqttClass FbMqttClass; +typedef struct _FbMqttPrivate FbMqttPrivate; +typedef struct _FbMqttMessage FbMqttMessage; +typedef struct _FbMqttMessageClass FbMqttMessageClass; +typedef struct _FbMqttMessagePrivate FbMqttMessagePrivate; + +/** + * FbMqttConnectFlags: + * @FB_MQTT_CONNECT_FLAG_CLR: Clear the session. + * @FB_MQTT_CONNECT_FLAG_WILL: A will message is in the payload. + * @FB_MQTT_CONNECT_FLAG_RET: Retain the will message. + * @FB_MQTT_CONNECT_FLAG_PASS: A password is in the payload. + * @FB_MQTT_CONNECT_FLAG_USER: A user name is in the payload. + * @FB_MQTT_CONNECT_FLAG_QOS0: Use no quality of service. + * @FB_MQTT_CONNECT_FLAG_QOS1: Use level one quality of service. + * @FB_MQTT_CONNECT_FLAG_QOS2: Use level two quality of service. + * + * The #FbMqttMessage flags for the CONNECT message. + */ +typedef enum +{ + FB_MQTT_CONNECT_FLAG_CLR = 1 << 1, + FB_MQTT_CONNECT_FLAG_WILL = 1 << 2, + FB_MQTT_CONNECT_FLAG_RET = 1 << 5, + FB_MQTT_CONNECT_FLAG_PASS = 1 << 6, + FB_MQTT_CONNECT_FLAG_USER = 1 << 7, + FB_MQTT_CONNECT_FLAG_QOS0 = 0 << 3, + FB_MQTT_CONNECT_FLAG_QOS1 = 1 << 3, + FB_MQTT_CONNECT_FLAG_QOS2 = 2 << 3 +} FbMqttConnectFlags; + +/** + * FbMqttError: + * @FB_MQTT_ERROR_SUCCESS: There is no error. + * @FB_MQTT_ERROR_PRTVERS: Unacceptable protocol version. + * @FB_MQTT_ERROR_IDREJECT: Identifier rejected. + * @FB_MQTT_ERROR_SRVGONE: Server unavailable. + * @FB_MQTT_ERROR_USERPASS: Bad user name or password. + * @FB_MQTT_ERROR_UNAUTHORIZED: Not authorized. + * @FB_MQTT_ERROR_GENERAL: General failure. + * + * The error codes for the #FB_MQTT_ERROR domain. + */ +typedef enum +{ + FB_MQTT_ERROR_SUCCESS = 0, + FB_MQTT_ERROR_PRTVERS = 1, + FB_MQTT_ERROR_IDREJECT = 2, + FB_MQTT_ERROR_SRVGONE = 3, + FB_MQTT_ERROR_USERPASS = 4, + FB_MQTT_ERROR_UNAUTHORIZED = 5, + FB_MQTT_ERROR_GENERAL +} FbMqttError; + +/** + * FbMqttMessageFlags: + * @FB_MQTT_MESSAGE_FLAG_RET: Retain messages. + * @FB_MQTT_MESSAGE_FLAG_DUP: Duplicate delivery of control packet. + * @FB_MQTT_MESSAGE_FLAG_QOS0: Use no quality of service. + * @FB_MQTT_MESSAGE_FLAG_QOS1: Use level one quality of service. + * @FB_MQTT_MESSAGE_FLAG_QOS2: Use level two quality of service. + * + * The #FbMqttMessage flags. + */ +typedef enum +{ + FB_MQTT_MESSAGE_FLAG_RET = 1 << 0, + FB_MQTT_MESSAGE_FLAG_DUP = 1 << 3, + FB_MQTT_MESSAGE_FLAG_QOS0 = 0 << 1, + FB_MQTT_MESSAGE_FLAG_QOS1 = 1 << 1, + FB_MQTT_MESSAGE_FLAG_QOS2 = 2 << 1 +} FbMqttMessageFlags; + +/** + * FbMqttMessageType: + * @FB_MQTT_MESSAGE_TYPE_CONNECT: Requests a connection. + * @FB_MQTT_MESSAGE_TYPE_CONNACK: Connection acknowledgment. + * @FB_MQTT_MESSAGE_TYPE_PUBLISH: Requests a message publication. + * @FB_MQTT_MESSAGE_TYPE_PUBACK: Publication acknowledgment. + * @FB_MQTT_MESSAGE_TYPE_PUBREC: Publication received. + * @FB_MQTT_MESSAGE_TYPE_PUBREL: Publication released. + * @FB_MQTT_MESSAGE_TYPE_PUBCOMP: Publication complete. + * @FB_MQTT_MESSAGE_TYPE_SUBSCRIBE: Requests a subscription. + * @FB_MQTT_MESSAGE_TYPE_SUBACK: Subscription acknowledgment. + * @FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE: Requests an unsubscription. + * @FB_MQTT_MESSAGE_TYPE_UNSUBACK: Unsubscription acknowledgment. + * @FB_MQTT_MESSAGE_TYPE_PINGREQ: Requests a ping response. + * @FB_MQTT_MESSAGE_TYPE_PINGRESP: Ping response. + * @FB_MQTT_MESSAGE_TYPE_DISCONNECT: Requests a disconnection. + * + * The #FbMqttMessage types. + */ +typedef enum +{ + FB_MQTT_MESSAGE_TYPE_CONNECT = 1, + FB_MQTT_MESSAGE_TYPE_CONNACK = 2, + FB_MQTT_MESSAGE_TYPE_PUBLISH = 3, + FB_MQTT_MESSAGE_TYPE_PUBACK = 4, + FB_MQTT_MESSAGE_TYPE_PUBREC = 5, + FB_MQTT_MESSAGE_TYPE_PUBREL = 6, + FB_MQTT_MESSAGE_TYPE_PUBCOMP = 7, + FB_MQTT_MESSAGE_TYPE_SUBSCRIBE = 8, + FB_MQTT_MESSAGE_TYPE_SUBACK = 9, + FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE = 10, + FB_MQTT_MESSAGE_TYPE_UNSUBACK = 11, + FB_MQTT_MESSAGE_TYPE_PINGREQ = 12, + FB_MQTT_MESSAGE_TYPE_PINGRESP = 13, + FB_MQTT_MESSAGE_TYPE_DISCONNECT = 14 +} FbMqttMessageType; + +/** + * FbMqtt: + * + * Represents an MQTT connection. + */ +struct _FbMqtt +{ + /*< private >*/ + GObject parent; + FbMqttPrivate *priv; +}; + +/** + * FbMqttClass: + * + * The base class for all #FbMqtt's. + */ +struct _FbMqttClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +/** + * FbMqttMessage: + * + * Represents a reader/writer for an MQTT message. + */ +struct _FbMqttMessage +{ + /*< private >*/ + GObject parent; + FbMqttMessagePrivate *priv; +}; + +/** + * FbMqttMessageClass: + * + * The base class for all #FbMqttMessageClass's. + */ +struct _FbMqttMessageClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +/** + * fb_mqtt_get_type: + * + * Returns: The #GType for an #FbMqtt. + */ +GType +fb_mqtt_get_type(void); + +/** + * fb_mqtt_message_get_type: + * + * Returns: The #GType for an #FbMqttMessage. + */ +GType +fb_mqtt_message_get_type(void); + +/** + * fb_mqtt_error_quark: + * + * Gets the #GQuark of the domain of MQTT errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_mqtt_error_quark(void); + +/** + * fb_mqtt_ssl_error_quark: + * + * Gets the #GQuark of the domain of MQTT SSL errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_mqtt_ssl_error_quark(void); + +/** + * fb_mqtt_new: + * @gc: The #PurpleConnection. + * + * Creates a new #FbMqtt. The returned #FbMqtt should be freed with + * #g_object_unref() when no longer needed. + * + * Returns: The new #FbMqtt. + */ +FbMqtt * +fb_mqtt_new(PurpleConnection *gc); + +/** + * fb_mqtt_close: + * @mqtt: The #FbMqtt. + * + * Closes the MQTT without sending the `DISCONNECT` message. The #FbMqtt + * may be reopened after calling this. + */ +void +fb_mqtt_close(FbMqtt *mqtt); + +/** + * fb_mqtt_error: + * @mqtt: The #FbMqtt. + * @error: The #FbMqttError. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Emits an #FbMqttError and closes the #FbMqtt. + */ +void +fb_mqtt_error(FbMqtt *mqtt, FbMqttError error, const gchar *format, ...) + G_GNUC_PRINTF(3, 4); + +/** + * fb_mqtt_read: + * @mqtt: The #FbMqtt. + * @msg: The #FbMqttMessage. + * + * Reads an #FbMqttMessage into the #FbMqtt for processing. + */ +void +fb_mqtt_read(FbMqtt *mqtt, FbMqttMessage *msg); + +/** + * fb_mqtt_write: + * @mqtt: The #FbMqtt. + * @msg: The #FbMqttMessage. + * + * Writes an #FbMqttMessage to the wire. + */ +void +fb_mqtt_write(FbMqtt *mqtt, FbMqttMessage *msg); + +/** + * fb_mqtt_open: + * @mqtt: The #FbMqtt. + * @host: The host name. + * @port: The port. + * + * Opens an SSL connection to the remote server. + */ +void +fb_mqtt_open(FbMqtt *mqtt, const gchar *host, gint port); + +/** + * fb_mqtt_connect: + * @mqtt: The #FbMqtt. + * @flags: The #FbMqttConnectFlags. + * @pload: The payload. + * + * Sends a message of type #FB_MQTT_MESSAGE_TYPE_CONNECT. + */ +void +fb_mqtt_connect(FbMqtt *mqtt, guint8 flags, const GByteArray *pload); + +/** + * fb_mqtt_connected: + * @mqtt: The #FbMqtt. + * @error: #TRUE to error with no connection, otherwise #FALSE. + * + * Determines the connection state of the #FbMqtt, and optionally emits + * an error. + * + * Returns: #TRUE if the #FbMqtt is connected, otherwise #FALSE. + */ +gboolean +fb_mqtt_connected(FbMqtt *mqtt, gboolean error); + +/** + * fb_mqtt_disconnect: + * @mqtt: The #FbMqtt. + * + * Sends a message of type #FB_MQTT_MESSAGE_TYPE_DISCONNECT, and closes + * the connection. + */ +void +fb_mqtt_disconnect(FbMqtt *mqtt); + +/** + * fb_mqtt_publish: + * @mqtt: The #FbMqtt. + * @topic: The topic. + * @pload: The payload. + * + * Sends a message of type #FB_MQTT_MESSAGE_TYPE_PUBLISH. + */ +void +fb_mqtt_publish(FbMqtt *mqtt, const gchar *topic, const GByteArray *pload); + +/** + * fb_mqtt_subscribe: + * @mqtt: The #FbMqtt. + * @topic1: The first topic. + * @qos1: The first QoS. + * @...: The %NULL-terminated list of topic/QoS pairs. + * + * Sends a message of type #FB_MQTT_MESSAGE_TYPE_SUBSCRIBE. + */ +void +fb_mqtt_subscribe(FbMqtt *mqtt, const gchar *topic1, guint16 qos1, ...) + G_GNUC_NULL_TERMINATED; + +/** + * fb_mqtt_unsubscribe: + * @mqtt: The #FbMqtt. + * @topic1: The first topic. + * @...: The %NULL-terminated list of topics. + * + * Sends a message of type #FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE. + */ +void +fb_mqtt_unsubscribe(FbMqtt *mqtt, const gchar *topic1, ...) + G_GNUC_NULL_TERMINATED; + +/** + * fb_mqtt_message_new: + * @type: The #FbMqttMessageType. + * @flags: The #FbMqttMessageFlags. + * + * Creates a new #FbMqttMessage. The returned #FbMqttMessage should be + * freed with #g_object_unref() when no longer needed. + * + * Returns: The new #FbMqttMessage. + */ +FbMqttMessage * +fb_mqtt_message_new(FbMqttMessageType type, FbMqttMessageFlags flags); + +/** + * fb_mqtt_message_new_bytes: + * @bytes: The #GByteArray. + * + * Creates a new #FbMqttMessage from a #GByteArray. The returned + * #FbMqttMessage should be freed with #g_object_unref() when no + * longer needed. + * + * Returns: The new #FbMqttMessage. + */ +FbMqttMessage * +fb_mqtt_message_new_bytes(GByteArray *bytes); + +/** + * fb_mqtt_message_reset: + * @msg: The #FbMqttMessage. + * + * Resets an #FbMqttMessage. This resets the cursor position, and + * removes any sort of fixed header. + */ +void +fb_mqtt_message_reset(FbMqttMessage *msg); + +/** + * fb_mqtt_message_bytes: + * @msg: The #FbMqttMessage. + * + * Formats the internal #GByteArray of the #FbMqttMessage with the + * required fixed header. This resets the cursor position. + * + * Returns: The internal #GByteArray. + */ +const GByteArray * +fb_mqtt_message_bytes(FbMqttMessage *msg); + +/** + * fb_mqtt_message_read: + * @msg: The #FbMqttMessage. + * @data: The data buffer. + * @size: The size of @buffer. + * + * Reads data from the #FbMqttMessage into a buffer. If @data is #NULL, + * this will simply advance the cursor position. + * + * Returns: #TRUE if the data was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read(FbMqttMessage *msg, gpointer data, guint size); + +/** + * fb_mqtt_message_read_r: + * @msg: The #FbMqttMessage. + * @bytes: The #GByteArray. + * + * Reads the remaining data from the #FbMqttMessage into a #GByteArray. + * This is useful for obtaining the payload of a message. + * + * Returns: #TRUE if the data was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read_r(FbMqttMessage *msg, GByteArray *bytes); + +/** + * fb_mqtt_message_read_byte: + * @msg: The #FbMqttMessage. + * @value: The return location for the value or #NULL. + * + * Reads an 8-bit integer value from the #FbMqttMessage. If @value is + * #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read_byte(FbMqttMessage *msg, guint8 *value); + +/** + * fb_mqtt_message_read_mid: + * @msg: The #FbMqttMessage. + * @value: The return location for the value or #NULL. + * + * Reads a message identifier from the #FbMqttMessage. If @value is + * #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read_mid(FbMqttMessage *msg, guint16 *value); + +/** + * fb_mqtt_message_read_u16: + * @msg: The #FbMqttMessage. + * @value: The return location for the value or #NULL. + * + * Reads a 16-bit integer value from the #FbMqttMessage. If @value is + * #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read_u16(FbMqttMessage *msg, guint16 *value); + +/** + * fb_mqtt_message_read_str: + * @msg: The #FbMqttMessage. + * @value: The return location for the value or #NULL. + * + * Reads a string value from the #FbMqttMessage. The value returned to + * @value should be freed with #g_free() when no longer needed. If + * @value is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read_str(FbMqttMessage *msg, gchar **value); + +/** + * fb_mqtt_message_write: + * @msg: The #FbMqttMessage. + * @data: The data buffer. + * @size: The size of @buffer. + * + * Writes data to the #FbMqttMessage. + */ +void +fb_mqtt_message_write(FbMqttMessage *msg, gconstpointer data, guint size); + +/** + * fb_mqtt_message_write_byte: + * @msg: The #FbMqttMessage. + * @value: The value. + * + * Writes an 8-bit integer value to the #FbMqttMessage. + */ +void +fb_mqtt_message_write_byte(FbMqttMessage *msg, guint8 value); + +/** + * fb_mqtt_message_write_mid: + * @msg: The #FbMqttMessage. + * @value: The value. + * + * Writes a message identifier to the #FbMqttMessage. This increments + * @value for the next message. + */ +void +fb_mqtt_message_write_mid(FbMqttMessage *msg, guint16 *value); + +/** + * fb_mqtt_message_write_u16: + * @msg: The #FbMqttMessage. + * @value: The value. + * + * Writes a 16-bit integer value to the #FbMqttMessage. + */ +void +fb_mqtt_message_write_u16(FbMqttMessage *msg, guint16 value); + +/** + * fb_mqtt_message_write_str: + * @msg: The #FbMqttMessage. + * @value: The value. + * + * Writes a string value to the #FbMqttMessage. + */ +void +fb_mqtt_message_write_str(FbMqttMessage *msg, const gchar *value); + +#endif /* _FACEBOOK_MQTT_H_ */ diff --git a/pidgin/libpurple/protocols/facebook/thrift.c b/pidgin/libpurple/protocols/facebook/thrift.c new file mode 100644 index 00000000..c4356f6f --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/thrift.c @@ -0,0 +1,700 @@ +/* purple + * + * 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 + +#include "glibcompat.h" +#include "thrift.h" + +struct _FbThriftPrivate +{ + GByteArray *bytes; + gboolean internal; + guint offset; + guint pos; + guint lastbool; +}; + +G_DEFINE_TYPE_WITH_CODE(FbThrift, fb_thrift, G_TYPE_OBJECT, G_ADD_PRIVATE(FbThrift)); + +static void +fb_thrift_dispose(GObject *obj) +{ + FbThriftPrivate *priv = FB_THRIFT(obj)->priv; + + if (priv->internal) { + g_byte_array_free(priv->bytes, TRUE); + } +} + +static void +fb_thrift_class_init(FbThriftClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); + + gklass->dispose = fb_thrift_dispose; + g_type_class_add_private(klass, sizeof (FbThriftPrivate)); +} + +static void +fb_thrift_init(FbThrift *thft) +{ + FbThriftPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(thft, FB_TYPE_THRIFT, + FbThriftPrivate); + thft->priv = priv; +} + +FbThrift * +fb_thrift_new(GByteArray *bytes, guint offset) +{ + FbThrift *thft; + FbThriftPrivate *priv; + + thft = g_object_new(FB_TYPE_THRIFT, NULL); + priv = thft->priv; + + if (bytes != NULL) { + priv->bytes = bytes; + priv->offset = offset; + priv->pos = offset; + } else { + priv->bytes = g_byte_array_new(); + priv->internal = TRUE; + } + + return thft; +} + +const GByteArray * +fb_thrift_get_bytes(FbThrift *thft) +{ + FbThriftPrivate *priv; + + g_return_val_if_fail(FB_IS_THRIFT(thft), NULL); + priv = thft->priv; + return priv->bytes; +} + +guint +fb_thrift_get_pos(FbThrift *thft) +{ + FbThriftPrivate *priv; + + g_return_val_if_fail(FB_IS_THRIFT(thft), 0); + priv = thft->priv; + return priv->pos; +} + +void +fb_thrift_set_pos(FbThrift *thft, guint pos) +{ + FbThriftPrivate *priv; + + g_return_if_fail(FB_IS_THRIFT(thft)); + priv = thft->priv; + priv->pos = pos; +} + +void +fb_thrift_reset(FbThrift *thft) +{ + FbThriftPrivate *priv; + + g_return_if_fail(FB_IS_THRIFT(thft)); + priv = thft->priv; + priv->pos = priv->offset; +} + +gboolean +fb_thrift_read(FbThrift *thft, gpointer data, guint size) +{ + FbThriftPrivate *priv; + + g_return_val_if_fail(FB_IS_THRIFT(thft), FALSE); + priv = thft->priv; + + if ((priv->pos + size) > priv->bytes->len) { + return FALSE; + } + + if ((data != NULL) && (size > 0)) { + memcpy(data, priv->bytes->data + priv->pos, size); + } + + priv->pos += size; + return TRUE; +} + +gboolean +fb_thrift_read_bool(FbThrift *thft, gboolean *value) +{ + FbThriftPrivate *priv; + guint8 byte; + + g_return_val_if_fail(FB_IS_THRIFT(thft), FALSE); + priv = thft->priv; + + if ((priv->lastbool & 0x03) != 0x01) { + if (!fb_thrift_read_byte(thft, &byte)) { + return FALSE; + } + + if (value != NULL) { + *value = (byte & 0x0F) == 0x01; + } + + priv->lastbool = 0; + return TRUE; + } + + if (value != NULL) { + *value = ((priv->lastbool & 0x04) >> 2) != 0; + } + + priv->lastbool = 0; + return TRUE; +} + +gboolean +fb_thrift_read_byte(FbThrift *thft, guint8 *value) +{ + return fb_thrift_read(thft, value, sizeof *value); +} + +gboolean +fb_thrift_read_dbl(FbThrift *thft, gdouble *value) +{ + gint64 i64; + + /* Almost always 8, but check anyways */ + static const gsize size = MIN(sizeof value, sizeof i64); + + if (!fb_thrift_read_i64(thft, &i64)) { + return FALSE; + } + + if (value != NULL) { + memcpy(value, &i64, size); + } + + return TRUE; +} + +gboolean +fb_thrift_read_i16(FbThrift *thft, gint16 *value) +{ + gint64 i64; + + if (!fb_thrift_read_i64(thft, &i64)) { + return FALSE; + } + + if (value != NULL) { + *value = i64; + } + + return TRUE; +} + +gboolean +fb_thrift_read_vi16(FbThrift *thft, guint16 *value) +{ + guint64 u64; + + if (!fb_thrift_read_vi64(thft, &u64)) { + return FALSE; + } + + if (value != NULL) { + *value = u64; + } + + return TRUE; +} + +gboolean +fb_thrift_read_i32(FbThrift *thft, gint32 *value) +{ + gint64 i64; + + if (!fb_thrift_read_i64(thft, &i64)) { + return FALSE; + } + + if (value != NULL) { + *value = i64; + } + + return TRUE; +} + +gboolean +fb_thrift_read_vi32(FbThrift *thft, guint32 *value) +{ + guint64 u64; + + if (!fb_thrift_read_vi64(thft, &u64)) { + return FALSE; + } + + if (value != NULL) { + *value = u64; + } + + return TRUE; +} + +gboolean +fb_thrift_read_i64(FbThrift *thft, gint64 *value) +{ + guint64 u64; + + if (!fb_thrift_read_vi64(thft, &u64)) { + return FALSE; + } + + if (value != NULL) { + /* Convert from zigzag to integer */ + *value = (u64 >> 0x01) ^ -(u64 & 0x01); + } + + return TRUE; +} + +gboolean +fb_thrift_read_vi64(FbThrift *thft, guint64 *value) +{ + guint i = 0; + guint8 byte; + guint64 u64 = 0; + + do { + if (!fb_thrift_read_byte(thft, &byte)) { + return FALSE; + } + + u64 |= ((guint64) (byte & 0x7F)) << i; + i += 7; + } while ((byte & 0x80) == 0x80); + + if (value != NULL) { + *value = u64; + } + + return TRUE; +} + +gboolean +fb_thrift_read_str(FbThrift *thft, gchar **value) +{ + guint8 *data; + guint32 size; + + if (!fb_thrift_read_vi32(thft, &size)) { + return FALSE; + } + + if (value != NULL) { + data = g_new(guint8, size + 1); + data[size] = 0; + } else { + data = NULL; + } + + if (!fb_thrift_read(thft, data, size)) { + g_free(data); + return FALSE; + } + + if (value != NULL) { + *value = (gchar *) data; + } + + return TRUE; +} + +gboolean +fb_thrift_read_field(FbThrift *thft, FbThriftType *type, gint16 *id, + gint16 lastid) +{ + FbThriftPrivate *priv; + gint16 i16; + guint8 byte; + + g_return_val_if_fail(FB_IS_THRIFT(thft), FALSE); + g_return_val_if_fail(type != NULL, FALSE); + g_return_val_if_fail(id != NULL, FALSE); + priv = thft->priv; + + if (!fb_thrift_read_byte(thft, &byte)) { + return FALSE; + } + + if (byte == FB_THRIFT_TYPE_STOP) { + *type = FB_THRIFT_TYPE_STOP; + return FALSE; + } + + *type = fb_thrift_ct2t(byte & 0x0F); + i16 = (byte & 0xF0) >> 4; + + if (i16 == 0) { + if (!fb_thrift_read_i16(thft, id)) { + return FALSE; + } + } else { + *id = lastid + i16; + } + + if (*type == FB_THRIFT_TYPE_BOOL) { + priv->lastbool = 0x01; + + if ((byte & 0x0F) == 0x01) { + priv->lastbool |= 0x01 << 2; + } + } + + return TRUE; +} + +gboolean +fb_thrift_read_stop(FbThrift *thft) +{ + guint8 byte; + + return fb_thrift_read_byte(thft, &byte) && + (byte == FB_THRIFT_TYPE_STOP); +} + +gboolean +fb_thrift_read_isstop(FbThrift *thft) +{ + FbThriftPrivate *priv; + guint8 byte; + + g_return_val_if_fail(FB_IS_THRIFT(thft), FALSE); + priv = thft->priv; + + if (!fb_thrift_read_byte(thft, &byte)) { + return FALSE; + } + + priv->pos--; + return byte == FB_THRIFT_TYPE_STOP; +} + +gboolean +fb_thrift_read_list(FbThrift *thft, FbThriftType *type, guint *size) +{ + guint8 byte; + guint32 u32; + + g_return_val_if_fail(type != NULL, FALSE); + g_return_val_if_fail(size != NULL, FALSE); + + if (!fb_thrift_read_byte(thft, &byte)) { + return FALSE; + } + + *type = fb_thrift_ct2t(byte & 0x0F); + *size = (byte & 0xF0) >> 4; + + if (*size == 0x0F) { + if (!fb_thrift_read_vi32(thft, &u32)) { + return FALSE; + } + + *size = u32; + } + + return TRUE; +} + +gboolean +fb_thrift_read_map(FbThrift *thft, FbThriftType *ktype, FbThriftType *vtype, + guint *size) +{ + gint32 i32; + guint8 byte; + + g_return_val_if_fail(ktype != NULL, FALSE); + g_return_val_if_fail(vtype != NULL, FALSE); + g_return_val_if_fail(size != NULL, FALSE); + + if (!fb_thrift_read_i32(thft, &i32)) { + return FALSE; + } + + if (i32 != 0) { + if (!fb_thrift_read_byte(thft, &byte)) { + return FALSE; + } + + *ktype = fb_thrift_ct2t((byte & 0xF0) >> 4); + *vtype = fb_thrift_ct2t(byte & 0x0F); + } else { + *ktype = 0; + *vtype = 0; + } + + *size = i32; + return TRUE; +} + +gboolean +fb_thrift_read_set(FbThrift *thft, FbThriftType *type, guint *size) +{ + return fb_thrift_read_list(thft, type, size); +} + +void +fb_thrift_write(FbThrift *thft, gconstpointer data, guint size) +{ + FbThriftPrivate *priv; + + g_return_if_fail(FB_IS_THRIFT(thft)); + priv = thft->priv; + + g_byte_array_append(priv->bytes, data, size); + priv->pos += size; +} + +void +fb_thrift_write_bool(FbThrift *thft, gboolean value) +{ + FbThriftPrivate *priv; + guint pos; + + g_return_if_fail(FB_IS_THRIFT(thft)); + priv = thft->priv; + + if ((priv->lastbool & 0x03) != 0x02) { + fb_thrift_write_byte(thft, value ? 0x01 : 0x02); + return; + } + + pos = priv->lastbool >> 3; + priv->lastbool = 0; + + if ((pos >= priv->offset) && (pos < priv->bytes->len)) { + priv->bytes->data[pos] &= ~0x0F; + priv->bytes->data[pos] |= value ? 0x01 : 0x02; + } +} + +void +fb_thrift_write_byte(FbThrift *thft, guint8 value) +{ + fb_thrift_write(thft, &value, sizeof value); +} + +void +fb_thrift_write_dbl(FbThrift *thft, gdouble value) +{ + gint64 i64; + + /* Almost always 8, but check anyways */ + static const gsize size = MIN(sizeof value, sizeof i64); + + memcpy(&i64, &value, size); + fb_thrift_write_i64(thft, i64); +} + +void +fb_thrift_write_i16(FbThrift *thft, gint16 value) +{ + fb_thrift_write_i64(thft, value); +} + +void +fb_thrift_write_vi16(FbThrift *thft, guint16 value) +{ + fb_thrift_write_vi64(thft, value); +} + +void +fb_thrift_write_i32(FbThrift *thft, gint32 value) +{ + value = (value << 1) ^ (value >> 31); + fb_thrift_write_vi64(thft, value); +} + +void +fb_thrift_write_vi32(FbThrift *thft, guint32 value) +{ + fb_thrift_write_vi64(thft, value); +} + +void +fb_thrift_write_i64(FbThrift *thft, gint64 value) +{ + value = (value << 1) ^ (value >> 63); + fb_thrift_write_vi64(thft, value); +} + +void +fb_thrift_write_vi64(FbThrift *thft, guint64 value) +{ + gboolean last; + guint8 byte; + + do { + last = (value & ~0x7F) == 0; + byte = value & 0x7F; + + if (!last) { + byte |= 0x80; + value >>= 7; + } + + fb_thrift_write_byte(thft, byte); + } while (!last); +} + +void +fb_thrift_write_str(FbThrift *thft, const gchar *value) +{ + guint32 size; + + g_return_if_fail(value != NULL); + + size = strlen(value); + fb_thrift_write_vi32(thft, size); + fb_thrift_write(thft, value, size); +} + +void +fb_thrift_write_field(FbThrift *thft, FbThriftType type, gint16 id, + gint16 lastid) +{ + FbThriftPrivate *priv; + gint16 diff; + + g_return_if_fail(FB_IS_THRIFT(thft)); + priv = thft->priv; + + if (type == FB_THRIFT_TYPE_BOOL) { + priv->lastbool = (priv->pos << 3) | 0x02; + } + + type = fb_thrift_t2ct(type); + diff = id - lastid; + + if ((id <= lastid) || (diff > 0x0F)) { + fb_thrift_write_byte(thft, type); + fb_thrift_write_i16(thft, id); + } else { + fb_thrift_write_byte(thft, (diff << 4) | type); + } +} + +void +fb_thrift_write_stop(FbThrift *thft) +{ + fb_thrift_write_byte(thft, FB_THRIFT_TYPE_STOP); +} + +void +fb_thrift_write_list(FbThrift *thft, FbThriftType type, guint size) +{ + type = fb_thrift_t2ct(type); + + if (size <= 14) { + fb_thrift_write_byte(thft, (size << 4) | type); + return; + } + + fb_thrift_write_vi32(thft, size); + fb_thrift_write_byte(thft, 0xF0 | type); +} + +void +fb_thrift_write_map(FbThrift *thft, FbThriftType ktype, FbThriftType vtype, + guint size) +{ + if (size == 0) { + fb_thrift_write_byte(thft, 0); + return; + } + + ktype = fb_thrift_t2ct(ktype); + vtype = fb_thrift_t2ct(vtype); + + fb_thrift_write_vi32(thft, size); + fb_thrift_write_byte(thft, (ktype << 4) | vtype); +} + +void +fb_thrift_write_set(FbThrift *thft, FbThriftType type, guint size) +{ + fb_thrift_write_list(thft, type, size); +} + +guint8 +fb_thrift_t2ct(FbThriftType type) +{ + static const guint8 types[] = { + [FB_THRIFT_TYPE_STOP] = 0, + [FB_THRIFT_TYPE_VOID] = 0, + [FB_THRIFT_TYPE_BOOL] = 2, + [FB_THRIFT_TYPE_BYTE] = 3, + [FB_THRIFT_TYPE_DOUBLE] = 7, + [5] = 0, + [FB_THRIFT_TYPE_I16] = 4, + [7] = 0, + [FB_THRIFT_TYPE_I32] = 5, + [9] = 0, + [FB_THRIFT_TYPE_I64] = 6, + [FB_THRIFT_TYPE_STRING] = 8, + [FB_THRIFT_TYPE_STRUCT] = 12, + [FB_THRIFT_TYPE_MAP] = 11, + [FB_THRIFT_TYPE_SET] = 10, + [FB_THRIFT_TYPE_LIST] = 9 + }; + + g_return_val_if_fail(type < G_N_ELEMENTS(types), 0); + return types[type]; +} + +FbThriftType +fb_thrift_ct2t(guint8 type) +{ + static const guint8 types[] = { + [0] = FB_THRIFT_TYPE_STOP, + [1] = FB_THRIFT_TYPE_BOOL, + [2] = FB_THRIFT_TYPE_BOOL, + [3] = FB_THRIFT_TYPE_BYTE, + [4] = FB_THRIFT_TYPE_I16, + [5] = FB_THRIFT_TYPE_I32, + [6] = FB_THRIFT_TYPE_I64, + [7] = FB_THRIFT_TYPE_DOUBLE, + [8] = FB_THRIFT_TYPE_STRING, + [9] = FB_THRIFT_TYPE_LIST, + [10] = FB_THRIFT_TYPE_SET, + [11] = FB_THRIFT_TYPE_MAP, + [12] = FB_THRIFT_TYPE_STRUCT + }; + + g_return_val_if_fail(type < G_N_ELEMENTS(types), 0); + return types[type]; +} diff --git a/pidgin/libpurple/protocols/facebook/thrift.h b/pidgin/libpurple/protocols/facebook/thrift.h new file mode 100644 index 00000000..0da34cac --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/thrift.h @@ -0,0 +1,604 @@ +/* purple + * + * 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 + */ + +#ifndef _FACEBOOK_THRIFT_H_ +#define _FACEBOOK_THRIFT_H_ + +/** + * SECTION:thrift + * @section_id: facebook-thrift + * @short_description: thrift.h + * @title: Thrift Reader/Writer + * + * The Thrift reader/writer. + */ + +#include +#include + +#define FB_TYPE_THRIFT (fb_thrift_get_type()) +#define FB_THRIFT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_THRIFT, FbThrift)) +#define FB_THRIFT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_THRIFT, FbThriftClass)) +#define FB_IS_THRIFT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_THRIFT)) +#define FB_IS_THRIFT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_THRIFT)) +#define FB_THRIFT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_THRIFT, FbThriftClass)) + +typedef struct _FbThrift FbThrift; +typedef struct _FbThriftClass FbThriftClass; +typedef struct _FbThriftPrivate FbThriftPrivate; + +/** + * FbThriftType: + * @FB_THRIFT_TYPE_STOP: A stopper for certain types. + * @FB_THRIFT_TYPE_VOID: A void or empty value. + * @FB_THRIFT_TYPE_BOOL: A boolean (#TRUE or #FALSE). + * @FB_THRIFT_TYPE_BYTE: A signed 8-bit integer. + * @FB_THRIFT_TYPE_DOUBLE: A 64-bit floating point number. + * @FB_THRIFT_TYPE_I16: A signed 16-bit integer. + * @FB_THRIFT_TYPE_I32: A signed 32-bit integer. + * @FB_THRIFT_TYPE_I64: A signed 64-bit integer. + * @FB_THRIFT_TYPE_STRING: A UTF-8 encoded string. + * @FB_THRIFT_TYPE_STRUCT: A set of typed fields. + * @FB_THRIFT_TYPE_MAP: A map of unique keys to values. + * @FB_THRIFT_TYPE_SET: A unique set of values. + * @FB_THRIFT_TYPE_LIST: A ordered list of values. + * @FB_THRIFT_TYPE_ENUM: A 32-bit enumerated list. + * @FB_THRIFT_TYPE_UNKNOWN: An unknown type. + * + * The Thrift data types. + */ +typedef enum +{ + FB_THRIFT_TYPE_STOP = 0, + FB_THRIFT_TYPE_VOID = 1, + FB_THRIFT_TYPE_BOOL = 2, + FB_THRIFT_TYPE_BYTE = 3, + FB_THRIFT_TYPE_DOUBLE = 4, + FB_THRIFT_TYPE_I16 = 6, + FB_THRIFT_TYPE_I32 = 8, + FB_THRIFT_TYPE_I64 = 10, + FB_THRIFT_TYPE_STRING = 11, + FB_THRIFT_TYPE_STRUCT = 12, + FB_THRIFT_TYPE_MAP = 13, + FB_THRIFT_TYPE_SET = 14, + FB_THRIFT_TYPE_LIST = 15, + FB_THRIFT_TYPE_ENUM = 16, + + FB_THRIFT_TYPE_UNKNOWN +} FbThriftType; + +/** + * FbThrift: + * + * Represents a reader/writer for compact Thrift data. + */ +struct _FbThrift +{ + /*< private >*/ + GObject parent; + FbThriftPrivate *priv; +}; + +/** + * FbThriftClass: + * + * The base class for all #FbThrift's. + */ +struct _FbThriftClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +/** + * fb_thrift_get_type: + * + * Returns: The #GType for an #FbThrift. + */ +GType +fb_thrift_get_type(void); + +/** + * fb_thrift_new: + * @bytes: The #GByteArray to read or write. + * @offset: The offset in bytes of the data in @bytes. + * + * Creates a new #FbThrift. The returned #FbThrift should be freed with + * #g_object_unref() when no longer needed. This will optionally use a + * #GByteArray at an offset, rather than a newly created and internal + * #GByteArray. + * + * Returns: The new #FbThrift. + */ +FbThrift * +fb_thrift_new(GByteArray *bytes, guint offset); + +/** + * fb_thrift_get_bytes: + * @thft: The #FbThrift. + * + * Gets the underlying #GByteArray of an #FbThrift. + * + * Returns: The #GByteArray. + */ +const GByteArray * +fb_thrift_get_bytes(FbThrift *thft); + +/** + * fb_thrift_get_pos: + * @thft: The #FbThrift. + * + * Gets the cursor position of an #FbThrift. + * + * Returns: The cursor position. + */ +guint +fb_thrift_get_pos(FbThrift *thft); + +/** + * fb_thrift_set_pos: + * @thft: The #FbThrift. + * @pos: The position. + * + * Sets the cursor position of an #FbThrift. + * + * Returns: The #GByteArray. + */ +void +fb_thrift_set_pos(FbThrift *thft, guint pos); + +/** + * fb_thrift_reset: + * @thft: The #FbThrift. + * + * Resets the cursor position of an #FbThrift. + * + * Returns: The #GByteArray. + */ +void +fb_thrift_reset(FbThrift *thft); + +/** + * fb_thrift_read: + * @thft: The #FbThrift. + * @data: The data buffer. + * @size: The size of @buffer. + * + * Reads data from the #FbThrift into a buffer. If @data is #NULL, this + * will simply advance the cursor position. + * + * Returns: #TRUE if the data was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read(FbThrift *thft, gpointer data, guint size); + +/** + * fb_thrift_read_bool: + * @thft: The #FbThrift. + * @value: The return location for the value or #NULL. + * + * Reads a boolean value from the #FbThrift. If @value is #NULL, this + * will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_bool(FbThrift *thft, gboolean *value); + +/** + * fb_thrift_read_byte: + * @thft: The #FbThrift. + * @value: The return location for the value or #NULL. + * + * Reads an 8-bit integer value from the #FbThrift. If @value is #NULL, + * this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_byte(FbThrift *thft, guint8 *value); + +/** + * fb_thrift_read_dbl: + * @thft: The #FbThrift. + * @value: The return location for the value or #NULL. + * + * Reads a 64-bit floating point value from the #FbThrift. If @value + * is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_dbl(FbThrift *thft, gdouble *value); + +/** + * fb_thrift_read_i16: + * @thft: The #FbThrift. + * @value: The return location for the value or #NULL. + * + * Reads a signed 16-bit integer value from the #FbThrift. This will + * convert the integer from the zig-zag format. If @value is #NULL, + * this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_i16(FbThrift *thft, gint16 *value); + +/** + * fb_thrift_read_vi16: + * @thft: The #FbThrift. + * @value: The return location for the value or #NULL. + * + * Reads a 16-bit integer value from the #FbThrift. This reads the raw + * integer value without converting it from the zig-zag format. If + * @value is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_vi16(FbThrift *thft, guint16 *value); + +/** + * fb_thrift_read_i32: + * @thft: The #FbThrift. + * @value: The return location for the value or #NULL. + * + * Reads a signed 32-bit integer value from the #FbThrift. This will + * convert the integer from the zig-zag format. If @value is #NULL, + * this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_i32(FbThrift *thft, gint32 *value); + +/** + * fb_thrift_read_vi32: + * @thft: The #FbThrift. + * @value: The return location for the value or #NULL. + * + * Reads a 32-bit integer value from the #FbThrift. This reads the raw + * integer value without converting it from the zig-zag format. If + * @value is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_vi32(FbThrift *thft, guint32 *value); + +/** + * fb_thrift_read_i64: + * @thft: The #FbThrift. + * @value: The return location for the value or #NULL. + * + * Reads a signed 64-bit integer value from the #FbThrift. This will + * convert the integer from the zig-zag format. If @value is #NULL, + * this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_i64(FbThrift *thft, gint64 *value); + +/** + * fb_thrift_read_vi64: + * @thft: The #FbThrift. + * @value: The return location for the value or #NULL. + * + * Reads a 64-bit integer value from the #FbThrift. This reads the raw + * integer value without converting it from the zig-zag format. If + * @value is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_vi64(FbThrift *thft, guint64 *value); + +/** + * fb_thrift_read_str: + * @thft: The #FbThrift. + * @value: The return location for the value or #NULL. + * + * Reads a string value from the #FbThrift. The value returned to + * @value should be freed with #g_free() when no longer needed. If + * @value is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_str(FbThrift *thft, gchar **value); + +/** + * fb_thrift_read_field: + * @thft: The #FbThrift. + * @type: The return location for the #FbThriftType. + * @id: The return location for the identifier. + * @lastid: The identifier of the previous field. + * + * Reads a field header from the #FbThrift. + * + * Returns: #TRUE if the field header was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_field(FbThrift *thft, FbThriftType *type, gint16 *id, + gint16 lastid); + +/** + * fb_thrift_read_stop: + * @thft: The #FbThrift. + * + * Reads a field stop from the #FbThrift. + * + * Returns: #TRUE if the field stop was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_stop(FbThrift *thft); + +/** + * fb_thrift_read_isstop: + * @thft: The #FbThrift. + * + * Determines if the next byte of the #FbThrift is a field stop. + * + * Returns: #TRUE if the next byte is a field stop, otherwise #FALSE. + */ +gboolean +fb_thrift_read_isstop(FbThrift *thft); + +/** + * fb_thrift_read_list: + * @thft: The #FbThrift. + * @type: The return location for the #FbThriftType. + * @size: The return location for the size. + * + * Reads a list header from the #FbThrift. + * + * Returns: #TRUE if the list header was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_list(FbThrift *thft, FbThriftType *type, guint *size); + +/** + * fb_thrift_read_map: + * @thft: The #FbThrift. + * @ktype: The return location for the key #FbThriftType. + * @vtype: The return location for the value #FbThriftType. + * @size: The return location for the size. + * + * Reads a map header from the #FbThrift. + * + * Returns: #TRUE if the map header was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_map(FbThrift *thft, FbThriftType *ktype, FbThriftType *vtype, + guint *size); + +/** + * fb_thrift_read_set: + * @thft: The #FbThrift. + * @type: The return location for the #FbThriftType. + * @size: The return location for the size. + * + * Reads a set header from the #FbThrift. + * + * Returns: #TRUE if the set header was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_set(FbThrift *thft, FbThriftType *type, guint *size); + +/** + * fb_thrift_write: + * @thft: The #FbThrift. + * @data: The data buffer. + * @size: The size of @buffer. + * + * Writes data to the #FbThrift. + */ +void +fb_thrift_write(FbThrift *thft, gconstpointer data, guint size); + +/** + * fb_thrift_write_bool: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a boolean value to the #FbThrift. + */ +void +fb_thrift_write_bool(FbThrift *thft, gboolean value); + +/** + * fb_thrift_write_byte: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes an 8-bit integer value to the #FbThrift. + */ +void +fb_thrift_write_byte(FbThrift *thft, guint8 value); + +/** + * fb_thrift_write_dbl: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a 64-bit floating point value to the #FbThrift. + */ +void +fb_thrift_write_dbl(FbThrift *thft, gdouble value); + +/** + * fb_thrift_write_i16: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a signed 16-bit integer value to the #FbThrift. This will + * convert the integer to the zig-zag format. + */ +void +fb_thrift_write_i16(FbThrift *thft, gint16 value); + +/** + * fb_thrift_write_vi16: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a 16-bit integer value to the #FbThrift. This writes the raw + * integer value without converting it to the zig-zag format. + */ +void +fb_thrift_write_vi16(FbThrift *thft, guint16 value); + +/** + * fb_thrift_write_i32: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a signed 32-bit integer value to the #FbThrift. This will + * convert the integer to the zig-zag format. + */ +void +fb_thrift_write_i32(FbThrift *thft, gint32 value); + +/** + * fb_thrift_write_vi32: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a 32-bit integer value to the #FbThrift. This writes the raw + * integer value without converting it to the zig-zag format. + */ +void +fb_thrift_write_vi32(FbThrift *thft, guint32 value); + +/** + * fb_thrift_write_i64: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a signed 64-bit integer value to the #FbThrift. This will + * convert the integer to the zig-zag format. + */ +void +fb_thrift_write_i64(FbThrift *thft, gint64 value); + +/** + * fb_thrift_write_vi64: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a 64-bit integer value to the #FbThrift. This writes the raw + * integer value without converting it to the zig-zag format. + */ +void +fb_thrift_write_vi64(FbThrift *thft, guint64 value); + +/** + * fb_thrift_write_str: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a string value to the #FbThrift. + */ +void +fb_thrift_write_str(FbThrift *thft, const gchar *value); + +/** + * fb_thrift_write_field: + * @thft: The #FbThrift. + * @type: The #FbThriftType. + * @id: The identifier. + * @lastid: The identifier of the previous field. + * + * Writes a field header to the #FbThrift. + */ +void +fb_thrift_write_field(FbThrift *thft, FbThriftType type, gint16 id, + gint16 lastid); + +/** + * fb_thrift_write_stop: + * @thft: The #FbThrift. + * + * Writes a field stop to the #FbThrift. + */ +void +fb_thrift_write_stop(FbThrift *thft); + +/** + * fb_thrift_write_list: + * @thft: The #FbThrift. + * @type: The #FbThriftType. + * @size: The size. + * + * Writes a list header to the #FbThrift. + */ +void +fb_thrift_write_list(FbThrift *thft, FbThriftType type, guint size); + +/** + * fb_thrift_write_map: + * @thft: The #FbThrift. + * @ktype: The key #FbThriftType. + * @vtype: The value #FbThriftType. + * @size: The size. + * + * Writes a map header to the #FbThrift. + */ +void +fb_thrift_write_map(FbThrift *thft, FbThriftType ktype, FbThriftType vtype, + guint size); + +/** + * fb_thrift_write_set: + * @thft: The #FbThrift. + * @type: The #FbThriftType. + * @size: The size. + * + * Writes a set header to the #FbThrift. + */ +void +fb_thrift_write_set(FbThrift *thft, FbThriftType type, guint size); + +/** + * fb_thrift_t2ct: + * @type: The #FbThriftType. + * + * Converts a #FbThriftType to a compact type. + * + * Return: The equivalent compact type. + */ +guint8 +fb_thrift_t2ct(FbThriftType type); + +/** + * fb_thrift_ct2t: + * @type: The compact type. + * + * Converts a compact type to an #FbThriftType. + * + * Return: The equivalent #FbThriftType. + */ +FbThriftType +fb_thrift_ct2t(guint8 type); + +#endif /* _FACEBOOK_THRIFT_H_ */ diff --git a/pidgin/libpurple/protocols/facebook/util.c b/pidgin/libpurple/protocols/facebook/util.c new file mode 100644 index 00000000..72890a5a --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/util.c @@ -0,0 +1,566 @@ +/* purple + * + * 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 +#include +#include + +#include "buddylist.h" +#include "conversations.h" +#include "glibcompat.h" +#include "message.h" +#include "request.h" +#include "server.h" + +#include "util.h" + +GQuark +fb_util_error_quark(void) +{ + static GQuark q = 0; + + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-util-error-quark"); + } + + return q; +} + +PurpleBuddy * +fb_util_account_find_buddy(PurpleAccount *acct, PurpleChatConversation *chat, + const gchar *search, GError **error) +{ + const gchar *alias; + const gchar *name; + GSList *buddies; + GSList *l; + guint retc; + PurpleBuddy *ret = NULL; + + g_return_val_if_fail(PURPLE_IS_ACCOUNT(acct), NULL); + g_return_val_if_fail(search != NULL, NULL); + + buddies = purple_blist_find_buddies(acct, NULL); + + for (retc = 0, l = buddies; l != NULL; l = l->next) { + name = purple_buddy_get_name(l->data); + alias = purple_buddy_get_alias(l->data); + + if ((chat != NULL) && + !purple_chat_conversation_has_user(chat, name)) + { + continue; + } + + if (g_ascii_strcasecmp(name, search) == 0) { + ret = l->data; + retc++; + } + + if (g_ascii_strcasecmp(alias, search) == 0) { + ret = l->data; + retc++; + } + } + + if (retc == 0) { + g_set_error(error, FB_UTIL_ERROR, FB_UTIL_ERROR_GENERAL, + _("Buddy %s not found"), search); + } else if (retc > 1) { + g_set_error(error, FB_UTIL_ERROR, FB_UTIL_ERROR_GENERAL, + _("Buddy name %s is ambiguous"), search); + ret = NULL; + } + + g_slist_free(buddies); + return ret; +} + +void +fb_util_debug(PurpleDebugLevel level, const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(level, format, ap); + va_end(ap); +} + +void +fb_util_vdebug(PurpleDebugLevel level, const gchar *format, va_list ap) +{ + gboolean unsafe; + gboolean verbose; + gchar *str; + + g_return_if_fail(format != NULL); + + unsafe = (level & FB_UTIL_DEBUG_FLAG_UNSAFE) != 0; + verbose = (level & FB_UTIL_DEBUG_FLAG_VERBOSE) != 0; + + if ((unsafe && !purple_debug_is_unsafe()) || + (verbose && !purple_debug_is_verbose())) + { + return; + } + + /* Ensure all local flags are removed */ + level &= ~FB_UTIL_DEBUG_FLAG_ALL; + + str = g_strdup_vprintf(format, ap); + purple_debug(level, "facebook", "%s\n", str); + g_free(str); +} + +void +fb_util_debug_misc(const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(PURPLE_DEBUG_MISC, format, ap); + va_end(ap); +} + +void +fb_util_debug_info(const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(PURPLE_DEBUG_INFO, format, ap); + va_end(ap); +} + +void +fb_util_debug_warning(const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(PURPLE_DEBUG_WARNING, format, ap); + va_end(ap); +} + +void +fb_util_debug_error(const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(PURPLE_DEBUG_ERROR, format, ap); + va_end(ap); +} + +void +fb_util_debug_fatal(const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(PURPLE_DEBUG_FATAL, format, ap); + va_end(ap); +} + +void +fb_util_debug_hexdump(PurpleDebugLevel level, const GByteArray *bytes, + const gchar *format, ...) +{ + gchar c; + guint i; + guint j; + GString *gstr; + va_list ap; + + static const gchar *indent = " "; + + g_return_if_fail(bytes != NULL); + + if (format != NULL) { + va_start(ap, format); + fb_util_vdebug(level, format, ap); + va_end(ap); + } + + gstr = g_string_sized_new(80); + + for (i = 0; i < bytes->len; i += 16) { + g_string_append_printf(gstr, "%s%08x ", indent, i); + + for (j = 0; j < 16; j++) { + if ((i + j) < bytes->len) { + g_string_append_printf(gstr, "%02x ", + bytes->data[i + j]); + } else { + g_string_append(gstr, " "); + } + + if (j == 7) { + g_string_append_c(gstr, ' '); + } + } + + g_string_append(gstr, " |"); + + for (j = 0; (j < 16) && ((i + j) < bytes->len); j++) { + c = bytes->data[i + j]; + + if (!g_ascii_isprint(c) || g_ascii_isspace(c)) { + c = '.'; + } + + g_string_append_c(gstr, c); + } + + g_string_append_c(gstr, '|'); + fb_util_debug(level, "%s", gstr->str); + g_string_erase(gstr, 0, -1); + } + + g_string_append_printf(gstr, "%s%08x", indent, i); + fb_util_debug(level, "%s", gstr->str); + g_string_free(gstr, TRUE); +} + +gchar * +fb_util_get_locale(void) +{ + const gchar * const *langs; + const gchar *lang; + gchar *chr; + guint i; + + static const gchar chrs[] = {'.', '@'}; + + langs = g_get_language_names(); + lang = langs[0]; + + if (purple_strequal(lang, "C")) { + return g_strdup("en_US"); + } + + for (i = 0; i < G_N_ELEMENTS(chrs); i++) { + chr = strchr(lang, chrs[i]); + + if (chr != NULL) { + return g_strndup(lang, chr - lang); + } + } + + return g_strdup(lang); +} + +gchar * +fb_util_rand_alnum(guint len) +{ + gchar *ret; + GRand *rand; + guint i; + guint j; + + static const gchar chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789"; + static const gsize charc = G_N_ELEMENTS(chars) - 1; + + g_return_val_if_fail(len > 0, NULL); + rand = g_rand_new(); + ret = g_new(gchar, len + 1); + + for (i = 0; i < len; i++) { + j = g_rand_int_range(rand, 0, charc); + ret[i] = chars[j]; + } + + ret[len] = 0; + g_rand_free(rand); + return ret; +} + +static void +fb_util_request_buddy_ok(gpointer *mata, PurpleRequestFields *fields) +{ + FbUtilRequestBuddyFunc func = mata[0]; + GList *l; + GList *select; + gpointer data = mata[2]; + GSList *ret = NULL; + PurpleBuddy *bdy; + PurpleRequestField *field; + + if (func == NULL) { + g_free(mata); + return; + } + + field = purple_request_fields_get_field(fields, "buddy"); + select = purple_request_field_list_get_selected(field); + + for (l = select; l != NULL; l = l->next) { + bdy = purple_request_field_list_get_data(field, l->data); + ret = g_slist_prepend(ret, bdy); + } + + ret = g_slist_reverse(ret); + func(ret, data); + + g_slist_free(ret); + g_free(mata); +} + +static void +fb_util_request_buddy_cancel(gpointer *mata, PurpleRequestFields *fields) +{ + FbUtilRequestBuddyFunc func = mata[1]; + gpointer data = mata[2]; + + if (func != NULL) { + func(NULL, data); + } + + g_free(mata); +} + +gpointer +fb_util_request_buddy(PurpleConnection *gc, const gchar *title, + const gchar *primary, const gchar *secondary, + GSList *select, gboolean multi, GCallback ok_cb, + GCallback cancel_cb, gpointer data) +{ + const gchar *alias; + const gchar *name; + gchar *str; + GList *items = NULL; + gpointer *mata; + GSList *buddies; + GSList *l; + PurpleAccount *acct; + PurpleRequestCommonParameters *cpar; + PurpleRequestField *field; + PurpleRequestFieldGroup *group; + PurpleRequestFields *fields; + + mata = g_new0(gpointer, 3); + mata[0] = ok_cb; + mata[1] = cancel_cb; + mata[2] = data; + + acct = purple_connection_get_account(gc); + buddies = purple_blist_find_buddies(acct, NULL); + buddies = g_slist_sort(buddies, (GCompareFunc) g_ascii_strcasecmp); + + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_list_new("buddy", NULL); + purple_request_field_list_set_multi_select(field, multi); + purple_request_field_set_required(field, TRUE); + purple_request_field_group_add_field(group, field); + + for (l = buddies; l != NULL; l = l->next) { + name = purple_buddy_get_name(l->data); + alias = purple_buddy_get_alias(l->data); + str = g_strdup_printf("%s (%s)", alias, name); + purple_request_field_list_add_icon(field, str, NULL, l->data); + g_free(str); + } + + for (l = select; l != NULL; l = l->next) { + name = purple_buddy_get_name(l->data); + alias = purple_buddy_get_alias(l->data); + str = g_strdup_printf("%s (%s)", alias, name); + items = g_list_append(items, str); + } + + purple_request_field_list_set_selected(field, items); + g_slist_free(buddies); + g_list_free_full(items, g_free); + + cpar = purple_request_cpar_from_connection(gc); + return purple_request_fields(gc, title, primary, secondary, fields, + _("Ok"), + G_CALLBACK(fb_util_request_buddy_ok), + _("Cancel"), + G_CALLBACK(fb_util_request_buddy_cancel), + cpar, mata); +} + +void +fb_util_serv_got_im(PurpleConnection *gc, const gchar *who, const gchar *text, + PurpleMessageFlags flags, guint64 timestamp) +{ + const gchar *name; + PurpleAccount *acct; + PurpleIMConversation *conv; + PurpleMessage *msg; + + if (!(flags & PURPLE_MESSAGE_SEND)) { + purple_serv_got_im(gc, who, text, flags, timestamp); + return; + } + + acct = purple_connection_get_account(gc); + conv = purple_conversations_find_im_with_account(who, acct); + + if (conv == NULL) { + conv = purple_im_conversation_new(acct, who); + } + + name = purple_account_get_username(acct); + msg = purple_message_new_outgoing(name, text, flags); + purple_message_set_time(msg, timestamp); + purple_conversation_write_message(PURPLE_CONVERSATION(conv), msg); +} + +void +fb_util_serv_got_chat_in(PurpleConnection *gc, gint id, const gchar *who, + const gchar *text, PurpleMessageFlags flags, + guint64 timestamp) +{ + const gchar *name; + PurpleAccount *acct; + PurpleChatConversation *conv; + PurpleMessage *msg; + + if (!(flags & PURPLE_MESSAGE_SEND)) { + purple_serv_got_chat_in(gc, id, who, flags, text, timestamp); + return; + } + + acct = purple_connection_get_account(gc); + conv = purple_conversations_find_chat(gc, id); + + name = purple_account_get_username(acct); + msg = purple_message_new_outgoing(name, text, flags); + purple_message_set_time(msg, timestamp); + purple_conversation_write_message(PURPLE_CONVERSATION(conv), msg); +} + +gboolean +fb_util_strtest(const gchar *str, GAsciiType type) +{ + gsize i; + gsize size; + guchar c; + + g_return_val_if_fail(str != NULL, FALSE); + size = strlen(str); + + for (i = 0; i < size; i++) { + c = (guchar) str[i]; + + if ((g_ascii_table[c] & type) == 0) { + return FALSE; + } + } + + return TRUE; +} + +gboolean +fb_util_zlib_test(const GByteArray *bytes) +{ + guint8 b0; + guint8 b1; + + g_return_val_if_fail(bytes != NULL, FALSE); + + if (bytes->len < 2) { + return FALSE; + } + + b0 = *(bytes->data + 0); + b1 = *(bytes->data + 1); + + return ((((b0 << 8) | b1) % 31) == 0) && /* Check the header */ + ((b0 & 0x0F) == 8 /* Z_DEFLATED */); /* Check the method */ +} + +static GByteArray * +fb_util_zlib_conv(GConverter *conv, const GByteArray *bytes, GError **error) +{ + GByteArray *ret; + GConverterResult res; + gsize cize = 0; + gsize rize; + gsize wize; + guint8 data[1024]; + + ret = g_byte_array_new(); + + while (TRUE) { + rize = 0; + wize = 0; + + res = g_converter_convert(conv, + bytes->data + cize, + bytes->len - cize, + data, sizeof data, + G_CONVERTER_INPUT_AT_END, + &rize, &wize, error); + + switch (res) { + case G_CONVERTER_CONVERTED: + g_byte_array_append(ret, data, wize); + cize += rize; + break; + + case G_CONVERTER_ERROR: + g_byte_array_free(ret, TRUE); + return NULL; + + case G_CONVERTER_FINISHED: + g_byte_array_append(ret, data, wize); + return ret; + + default: + break; + } + } +} + +GByteArray * +fb_util_zlib_deflate(const GByteArray *bytes, GError **error) +{ + GByteArray *ret; + GZlibCompressor *conv; + + conv = g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_ZLIB, -1); + ret = fb_util_zlib_conv(G_CONVERTER(conv), bytes, error); + g_object_unref(conv); + return ret; +} + +GByteArray * +fb_util_zlib_inflate(const GByteArray *bytes, GError **error) +{ + GByteArray *ret; + GZlibDecompressor *conv; + + conv = g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_ZLIB); + ret = fb_util_zlib_conv(G_CONVERTER(conv), bytes, error); + g_object_unref(conv); + return ret; +} diff --git a/pidgin/libpurple/protocols/facebook/util.h b/pidgin/libpurple/protocols/facebook/util.h new file mode 100644 index 00000000..2a1d5ebf --- /dev/null +++ b/pidgin/libpurple/protocols/facebook/util.h @@ -0,0 +1,350 @@ +/* purple + * + * 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 + */ + +#ifndef _FACEBOOK_UTIL_H_ +#define _FACEBOOK_UTIL_H_ + +/** + * SECTION:util + * @section_id: facebook-util + * @short_description: util.h + * @title: General Utilities + * + * The general utilities. + */ + +#include + +#include + +#include "account.h" +#include "conversationtypes.h" +#include "debug.h" +#include "connection.h" +#include "conversation.h" + +/** + * FB_UTIL_DEBUG_INFO: + * + * Shortcut #PurpleDebugLevel for unsafe and verbose info messages. + */ +#define FB_UTIL_DEBUG_INFO ( \ + PURPLE_DEBUG_INFO | \ + FB_UTIL_DEBUG_FLAG_UNSAFE | \ + FB_UTIL_DEBUG_FLAG_VERBOSE \ + ) + +/** + * FB_UTIL_ERROR: + * + * The #GQuark of the domain of utility errors. + */ +#define FB_UTIL_ERROR fb_util_error_quark() + +/** + * FbUtilRequestBuddyFunc: + * @buddies: The list of #PurpleBuddy's. + * @data: The user-defined data. + * + * The callback for requested buddies. + */ +typedef void (*FbUtilRequestBuddyFunc) (GSList *buddies, gpointer data); + +/** + * FbUtilDebugFlags: + * @FB_UTIL_DEBUG_FLAG_UNSAFE: The message is unsafe. + * @FB_UTIL_DEBUG_FLAG_VERBOSE: The message is verbose. + * @FB_UTIL_DEBUG_FLAG_ALL: All of the flags. + * + * The debugging message flags. These flags are inserted on top of + * a #PurpleDebugLevel. + */ +typedef enum +{ + FB_UTIL_DEBUG_FLAG_UNSAFE = 1 << 25, + FB_UTIL_DEBUG_FLAG_VERBOSE = 1 << 26, + FB_UTIL_DEBUG_FLAG_ALL = 3 << 25 +} FbUtilDebugFlags; + +/** + * FbUtilError: + * @FB_UTIL_ERROR_GENERAL: General failure. + * + * The error codes for the #FB_UTIL_ERROR domain. + */ +typedef enum +{ + FB_UTIL_ERROR_GENERAL +} FbUtilError; + +/** + * fb_util_error_quark: + * + * Gets the #GQuark of the domain of utility errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_util_error_quark(void); + +/** + * fb_util_account_find_buddy: + * @acct: The #PurpleAccount. + * @chat: The #PurpleChatConversation. + * @name: The name of the buddy. + * @error: The return location for the #GError or #NULL. + * + * Finds a buddy by their name or alias. + * + * Returns: The #PurpleBuddy if found, otherwise #NULL. + */ +PurpleBuddy * +fb_util_account_find_buddy(PurpleAccount *acct, PurpleChatConversation *chat, + const gchar *name, GError **error); + +/** + * fb_util_debug: + * @level: The #PurpleDebugLevel. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message. If the messages is unsafe or verbose, + * apply the appropriate #FbUtilDebugFlags. + */ +void +fb_util_debug(PurpleDebugLevel level, const gchar *format, ...) + G_GNUC_PRINTF(2, 3); + +/** + * fb_util_vdebug: + * @level: The #PurpleDebugLevel. + * @format: The format string literal. + * @ap: The #va_list. + * + * Logs a debugging message. If the messages is unsafe or verbose, + * apply the appropriate #FbUtilDebugFlags. + */ +void +fb_util_vdebug(PurpleDebugLevel level, const gchar *format, va_list ap); + +/** + * fb_util_debug_misc: + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message with the level of #PURPLE_DEBUG_MISC. + */ +void +fb_util_debug_misc(const gchar *format, ...) + G_GNUC_PRINTF(1, 2); + +/** + * fb_util_debug_info: + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message with the level of #PURPLE_DEBUG_INFO. + */ +void +fb_util_debug_info(const gchar *format, ...) + G_GNUC_PRINTF(1, 2); + +/** + * fb_util_debug_warning: + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message with the level of #PURPLE_DEBUG_WARNING. + */ +void +fb_util_debug_warning(const gchar *format, ...) + G_GNUC_PRINTF(1, 2); + +/** + * fb_util_debug_error: + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message with the level of #PURPLE_DEBUG_ERROR. + */ +void +fb_util_debug_error(const gchar *format, ...) + G_GNUC_PRINTF(1, 2); + +/** + * fb_util_debug_fatal: + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message with the level of #PURPLE_DEBUG_FATAL. + */ +void +fb_util_debug_fatal(const gchar *format, ...) + G_GNUC_PRINTF(1, 2); + +/** + * fb_util_debug_hexdump: + * @level: The #PurpleDebugLevel. + * @bytes: The #GByteArray. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a hexdump of a #GByteArray. If the messages is unsafe or + * verbose, apply the appropriate #FbUtilDebugFlags. + */ +void +fb_util_debug_hexdump(PurpleDebugLevel level, const GByteArray *bytes, + const gchar *format, ...) + G_GNUC_PRINTF(3, 4); + +/** + * fb_util_get_locale: + * + * Gets the locale string (ex: en_US) from the system. The returned + * string should be freed with #g_free() when no longer needed. + * + * Returns: The locale string. + */ +gchar * +fb_util_get_locale(void); + +/** + * fb_util_rand_alnum: + * @len: The length of the string. + * + * Gets a random alphanumeric (A-Za-z0-9) string. This function should + * *not* be relied on for cryptographic operations. The returned string + * should be freed with #g_free() when no longer needed. + * + * Returns: The alphanumeric string. + */ +gchar * +fb_util_rand_alnum(guint len); + +/** + * fb_util_request_buddy: + * @gc: The #PurpleConnection. + * @title: The title of the message or #NULL. + * @primary: The main point of the message or #NULL. + * @secondary: The secondary information or #NULL. + * @select: A #GSList of selected buddies or #NULL. + * @multi: #TRUE to for multiple buddy selections, otherwise #FALSE. + * @ok_cb: The callback for the `OK` button or #NULL. + * @cancel_cb: The callback for the `Cancel` button or #NULL. + * @data: The user-defined data. + * + * Displays a buddy list selection form. + * + * Returns: The UI-specific handle. + */ +gpointer +fb_util_request_buddy(PurpleConnection *gc, const gchar *title, + const gchar *primary, const gchar *secondary, + GSList *select, gboolean multi, GCallback ok_cb, + GCallback cancel_cb, gpointer data); + +/** + * fb_util_serv_got_im: + * @gc: The #PurpleConnection. + * @who: The message sender or receiver. + * @text: The message text. + * @flags: The #PurpleMessageFlags. + * @timestamp: The message timestamp. + * + * Handles an incoming IM message. This function is special in that it + * handles self messages. This function determines the direction of the + * message from the #PurpleMessageFlags. + */ +void +fb_util_serv_got_im(PurpleConnection *gc, const gchar *who, const gchar *text, + PurpleMessageFlags flags, guint64 timestamp); + +/** + * fb_util_serv_got_chat_in: + * @gc: The #PurpleConnection. + * @id: The id of the chat. + * @who: The message sender or receiver. + * @text: The message text. + * @flags: The #PurpleMessageFlags. + * @timestamp: The message timestamp. + * + * Handles an incoming chat message. This function is special in that + * it handles self messages. This function determines the direction of + * the message from the #PurpleMessageFlags. + */ +void +fb_util_serv_got_chat_in(PurpleConnection *gc, gint id, const gchar *who, + const gchar *text, PurpleMessageFlags flags, + guint64 timestamp); + +/** + * fb_util_strtest: + * @str: The string. + * @type: The #GAsciiType. + * + * Tests if the string only contains characters allowed by the + * #GAsciiType. More than one type can be specified by ORing the types + * together. + * + * Returns: #TRUE if the string only contains characters allowed by the + * #GAsciiType, otherwise #FALSE. + */ +gboolean +fb_util_strtest(const gchar *str, GAsciiType type); + +/** + * fb_util_zlib_test: + * @bytes: The #GByteArray. + * + * Tests if the #GByteArray is zlib compressed. + * + * Returns: #TRUE if the #GByteArray is compressed, otherwise #FALSE. + */ +gboolean +fb_util_zlib_test(const GByteArray *bytes); + +/** + * fb_util_zlib_deflate: + * @bytes: The #GByteArray. + * @error: The return location for the #GError or #NULL. + * + * Deflates a #GByteArray with zlib. The returned #GByteArray should be + * freed with #g_byte_array_free() when no longer needed. + * + * Returns: The deflated #GByteArray or #NULL on error. + */ +GByteArray * +fb_util_zlib_deflate(const GByteArray *bytes, GError **error); + +/** + * fb_util_zlib_inflate: + * @bytes: The #GByteArray. + * @error: The return location for the #GError or #NULL. + * + * Inflates a #GByteArray with zlib. The returned #GByteArray should be + * freed with #g_byte_array_free() when no longer needed. + * + * Returns: The inflated #GByteArray or #NULL on error. + */ +GByteArray * +fb_util_zlib_inflate(const GByteArray *bytes, GError **error); + +#endif /* _FACEBOOK_UTIL_H_ */ diff --git a/pidgin/libpurple/purple-socket.c b/pidgin/libpurple/purple-socket.c new file mode 100644 index 00000000..72b368b9 --- /dev/null +++ b/pidgin/libpurple/purple-socket.c @@ -0,0 +1,410 @@ +/* purple + * + * 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 "purple-socket.h" + +#include "internal.h" + +#include "debug.h" +#include "proxy.h" +#include "sslconn.h" + +typedef enum { + PURPLE_SOCKET_STATE_DISCONNECTED = 0, + PURPLE_SOCKET_STATE_CONNECTING, + PURPLE_SOCKET_STATE_CONNECTED, + PURPLE_SOCKET_STATE_ERROR +} PurpleSocketState; + +struct _PurpleSocket +{ + PurpleConnection *gc; + gchar *host; + int port; + gboolean is_tls; + GHashTable *data; + + PurpleSocketState state; + + PurpleSslConnection *tls_connection; + PurpleProxyConnectData *raw_connection; + int fd; + guint inpa; + + PurpleSocketConnectCb cb; + gpointer cb_data; +}; + +static GHashTable *handles = NULL; + +static void +handle_add(PurpleSocket *ps) +{ + PurpleConnection *gc = ps->gc; + GSList *l; + + l = g_hash_table_lookup(handles, gc); + l = g_slist_prepend(l, ps); + g_hash_table_insert(handles, gc, l); +} + +static void +handle_remove(PurpleSocket *ps) +{ + PurpleConnection *gc = ps->gc; + GSList *l; + + l = g_hash_table_lookup(handles, gc); + l = g_slist_remove(l, ps); + g_hash_table_insert(handles, gc, l); +} + +void +_purple_socket_init(void) +{ + handles = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +void +_purple_socket_uninit(void) +{ + g_hash_table_destroy(handles); + handles = NULL; +} + +PurpleSocket * +purple_socket_new(PurpleConnection *gc) +{ + PurpleSocket *ps = g_new0(PurpleSocket, 1); + + ps->gc = gc; + ps->fd = -1; + ps->port = -1; + ps->data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + handle_add(ps); + + return ps; +} + +PurpleConnection * +purple_socket_get_connection(PurpleSocket *ps) +{ + g_return_val_if_fail(ps != NULL, NULL); + + return ps->gc; +} + +static gboolean +purple_socket_check_state(PurpleSocket *ps, PurpleSocketState wanted_state) +{ + g_return_val_if_fail(ps != NULL, FALSE); + + if (ps->state == wanted_state) + return TRUE; + + purple_debug_error("socket", "invalid state: %d (should be: %d)", + ps->state, wanted_state); + ps->state = PURPLE_SOCKET_STATE_ERROR; + return FALSE; +} + +void +purple_socket_set_tls(PurpleSocket *ps, gboolean is_tls) +{ + g_return_if_fail(ps != NULL); + + if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) + return; + + ps->is_tls = is_tls; +} + +void +purple_socket_set_host(PurpleSocket *ps, const gchar *host) +{ + g_return_if_fail(ps != NULL); + + if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) + return; + + g_free(ps->host); + ps->host = g_strdup(host); +} + +void +purple_socket_set_port(PurpleSocket *ps, int port) +{ + g_return_if_fail(ps != NULL); + g_return_if_fail(port >= 0); + g_return_if_fail(port <= 65535); + + if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) + return; + + ps->port = port; +} + +static void +_purple_socket_connected_raw(gpointer _ps, gint fd, const gchar *error_message) +{ + PurpleSocket *ps = _ps; + + ps->raw_connection = NULL; + + if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTING)) { + if (fd > 0) + close(fd); + ps->cb(ps, _("Invalid socket state"), ps->cb_data); + return; + } + + if (fd <= 0 || error_message != NULL) { + if (error_message == NULL) + error_message = _("Unknown error"); + ps->fd = -1; + ps->state = PURPLE_SOCKET_STATE_ERROR; + ps->cb(ps, error_message, ps->cb_data); + return; + } + + ps->state = PURPLE_SOCKET_STATE_CONNECTED; + ps->fd = fd; + ps->cb(ps, NULL, ps->cb_data); +} + +static void +_purple_socket_connected_tls(gpointer _ps, PurpleSslConnection *tls_connection, + PurpleInputCondition cond) +{ + PurpleSocket *ps = _ps; + + if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTING)) { + purple_ssl_close(tls_connection); + ps->tls_connection = NULL; + ps->cb(ps, _("Invalid socket state"), ps->cb_data); + return; + } + + if (ps->tls_connection->fd <= 0) { + ps->state = PURPLE_SOCKET_STATE_ERROR; + purple_ssl_close(tls_connection); + ps->tls_connection = NULL; + ps->cb(ps, _("Invalid file descriptor"), ps->cb_data); + return; + } + + ps->state = PURPLE_SOCKET_STATE_CONNECTED; + ps->fd = ps->tls_connection->fd; + ps->cb(ps, NULL, ps->cb_data); +} + +static void +_purple_socket_connected_tls_error(PurpleSslConnection *ssl_connection, + PurpleSslErrorType error, gpointer _ps) +{ + PurpleSocket *ps = _ps; + + ps->state = PURPLE_SOCKET_STATE_ERROR; + ps->tls_connection = NULL; + ps->cb(ps, purple_ssl_strerror(error), ps->cb_data); +} + +gboolean +purple_socket_connect(PurpleSocket *ps, PurpleSocketConnectCb cb, + gpointer user_data) +{ + PurpleAccount *account = NULL; + + g_return_val_if_fail(ps != NULL, FALSE); + + if (ps->gc && purple_connection_is_disconnecting(ps->gc)) { + purple_debug_error("socket", "connection is being destroyed"); + ps->state = PURPLE_SOCKET_STATE_ERROR; + return FALSE; + } + + if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) + return FALSE; + ps->state = PURPLE_SOCKET_STATE_CONNECTING; + + if (ps->host == NULL || ps->port < 0) { + purple_debug_error("socket", "Host or port is not specified"); + ps->state = PURPLE_SOCKET_STATE_ERROR; + return FALSE; + } + + if (ps->gc != NULL) + account = purple_connection_get_account(ps->gc); + + ps->cb = cb; + ps->cb_data = user_data; + + if (ps->is_tls) { + ps->tls_connection = purple_ssl_connect(account, ps->host, + ps->port, _purple_socket_connected_tls, + _purple_socket_connected_tls_error, ps); + } else { + ps->raw_connection = purple_proxy_connect(ps->gc, account, + ps->host, ps->port, _purple_socket_connected_raw, ps); + } + + if (ps->tls_connection == NULL && + ps->raw_connection == NULL) + { + ps->state = PURPLE_SOCKET_STATE_ERROR; + return FALSE; + } + + return TRUE; +} + +gssize +purple_socket_read(PurpleSocket *ps, guchar *buf, size_t len) +{ + g_return_val_if_fail(ps != NULL, -1); + g_return_val_if_fail(buf != NULL, -1); + + if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) + return -1; + + if (ps->is_tls) + return purple_ssl_read(ps->tls_connection, buf, len); + else + return read(ps->fd, buf, len); +} + +gssize +purple_socket_write(PurpleSocket *ps, const guchar *buf, size_t len) +{ + g_return_val_if_fail(ps != NULL, -1); + g_return_val_if_fail(buf != NULL, -1); + + if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) + return -1; + + if (ps->is_tls) + return purple_ssl_write(ps->tls_connection, buf, len); + else + return write(ps->fd, buf, len); +} + +void +purple_socket_watch(PurpleSocket *ps, PurpleInputCondition cond, + PurpleInputFunction func, gpointer user_data) +{ + g_return_if_fail(ps != NULL); + + if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) + return; + + if (ps->inpa > 0) + purple_input_remove(ps->inpa); + ps->inpa = 0; + + g_return_if_fail(ps->fd > 0); + + if (func != NULL) + ps->inpa = purple_input_add(ps->fd, cond, func, user_data); +} + +int +purple_socket_get_fd(PurpleSocket *ps) +{ + g_return_val_if_fail(ps != NULL, -1); + + if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) + return -1; + + g_return_val_if_fail(ps->fd > 0, -1); + + return ps->fd; +} + +void +purple_socket_set_data(PurpleSocket *ps, const gchar *key, gpointer data) +{ + g_return_if_fail(ps != NULL); + g_return_if_fail(key != NULL); + + if (data == NULL) + g_hash_table_remove(ps->data, key); + else + g_hash_table_insert(ps->data, g_strdup(key), data); +} + +gpointer +purple_socket_get_data(PurpleSocket *ps, const gchar *key) +{ + g_return_val_if_fail(ps != NULL, NULL); + g_return_val_if_fail(key != NULL, NULL); + + return g_hash_table_lookup(ps->data, key); +} + +static void +purple_socket_cancel(PurpleSocket *ps) +{ + if (ps->inpa > 0) + purple_input_remove(ps->inpa); + ps->inpa = 0; + + if (ps->tls_connection != NULL) { + purple_ssl_close(ps->tls_connection); + ps->fd = -1; + } + ps->tls_connection = NULL; + + if (ps->raw_connection != NULL) + purple_proxy_connect_cancel(ps->raw_connection); + ps->raw_connection = NULL; + + if (ps->fd > 0) + close(ps->fd); + ps->fd = 0; +} + +void +purple_socket_destroy(PurpleSocket *ps) +{ + if (ps == NULL) + return; + + handle_remove(ps); + + purple_socket_cancel(ps); + + g_free(ps->host); + g_hash_table_destroy(ps->data); + g_free(ps); +} + +void +_purple_socket_cancel_with_connection(PurpleConnection *gc) +{ + GSList *it; + + it = g_hash_table_lookup(handles, gc); + for (; it; it = g_slist_next(it)) { + PurpleSocket *ps = it->data; + purple_socket_cancel(ps); + } +} diff --git a/pidgin/libpurple/purple-socket.h b/pidgin/libpurple/purple-socket.h new file mode 100644 index 00000000..b43e512e --- /dev/null +++ b/pidgin/libpurple/purple-socket.h @@ -0,0 +1,217 @@ +/* purple + * + * 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 + */ + +#ifndef _PURPLE_SOCKET_H_ +#define _PURPLE_SOCKET_H_ +/** + * SECTION:purple-socket + * @section_id: libpurple-purple-socket + * @short_description: purple-socket.h + * @title: Generic Sockets + */ + +#include "connection.h" + +/** + * PurpleSocket: + * + * A structure holding all resources needed for the TCP connection. + */ +typedef struct _PurpleSocket PurpleSocket; + +/** + * PurpleSocketConnectCb: + * @ps: The socket. + * @error: Error message, or NULL if connection was successful. + * @user_data: The user data passed with callback function. + * + * A callback fired after (successfully or not) establishing a connection. + */ +typedef void (*PurpleSocketConnectCb)(PurpleSocket *ps, const gchar *error, + gpointer user_data); + +/** + * purple_socket_new: + * @gc: The connection for which the socket is needed, or NULL. + * + * Creates new, disconnected socket. + * + * Passing a PurpleConnection allows for proper proxy handling. + * + * Returns: The new socket struct. + */ +PurpleSocket * +purple_socket_new(PurpleConnection *gc); + +/** + * purple_socket_get_connection: + * @ps: The socket. + * + * Gets PurpleConnection tied with specified socket. + * + * Returns: The PurpleConnection object. + */ +PurpleConnection * +purple_socket_get_connection(PurpleSocket *ps); + +/** + * purple_socket_set_tls: + * @ps: The socket. + * @is_tls: TRUE, if TLS should be handled transparently, FALSE otherwise. + * + * Determines, if socket should handle TLS. + */ +void +purple_socket_set_tls(PurpleSocket *ps, gboolean is_tls); + +/** + * purple_socket_set_host: + * @ps: The socket. + * @host: The connection host. + * + * Sets connection host. + */ +void +purple_socket_set_host(PurpleSocket *ps, const gchar *host); + +/** + * purple_socket_set_port: + * @ps: The socket. + * @port: The connection port. + * + * Sets connection port. + */ +void +purple_socket_set_port(PurpleSocket *ps, int port); + +/** + * purple_socket_connect: + * @ps: The socket. + * @cb: The function to call after establishing a connection, or on + * error. + * @user_data: The user data to be passed to callback function. + * + * Establishes a connection. + * + * Returns: TRUE on success (this doesn't mean it's connected yet), FALSE + * otherwise. + */ +gboolean +purple_socket_connect(PurpleSocket *ps, PurpleSocketConnectCb cb, + gpointer user_data); + +/** + * purple_socket_read: + * @ps: The socket. + * @buf: The buffer to write data to. + * @len: The buffer size. + * + * Reads incoming data from socket. + * + * This function deals with TLS, if the socket is configured to do it. + * + * Returns: Amount of data written, or -1 on error (errno will be also be set). + */ +gssize +purple_socket_read(PurpleSocket *ps, guchar *buf, size_t len); + +/** + * purple_socket_write: + * @ps: The socket. + * @buf: The buffer to read data from. + * @len: The amount of data to read and send. + * + * Sends data through socket. + * + * This function deals with TLS, if the socket is configured to do it. + * + * Returns: Amount of data sent, or -1 on error (errno will albo be set). + */ +gssize +purple_socket_write(PurpleSocket *ps, const guchar *buf, size_t len); + +/** + * purple_socket_watch: + * @ps: The socket. + * @cond: The condition type. + * @func: The callback function for data, or NULL to remove any + * existing callbacks. + * @user_data: The user data to be passed to callback function. + * + * Adds an input handler for the socket. + * + * If the specified socket had input handler already registered, it will be + * removed. To remove any input handlers, pass an NULL handler function. + */ +void +purple_socket_watch(PurpleSocket *ps, PurpleInputCondition cond, + PurpleInputFunction func, gpointer user_data); + +/** + * purple_socket_get_fd: + * @ps: The socket + * + * Gets underlying file descriptor for socket. + * + * It's not meant to read/write data (use purple_socket_read/ + * purple_socket_write), rather for watching for changes with select(). + * + * Returns: The file descriptor, or -1 on error. + */ +int +purple_socket_get_fd(PurpleSocket *ps); + +/** + * purple_socket_set_data: + * @ps: The socket. + * @key: The unique key. + * @data: The data to assign, or NULL to remove. + * + * Sets extra data for a socket. + */ +void +purple_socket_set_data(PurpleSocket *ps, const gchar *key, gpointer data); + +/** + * purple_socket_get_data: + * @ps: The socket. + * @key: The unqiue key. + * + * Returns extra data in a socket. + * + * Returns: The data associated with the key. + */ +gpointer +purple_socket_get_data(PurpleSocket *ps, const gchar *key); + +/** + * purple_socket_destroy: + * @ps: The socket. + * + * Destroys the socket, closes connection and frees all resources. + * + * If file descriptor for the socket was extracted with purple_socket_get_fd and + * added to event loop, it have to be removed prior this. + */ +void +purple_socket_destroy(PurpleSocket *ps); + +#endif /* _PURPLE_SOCKET_H_ */ From 6e625fbe54fcd10339942134d4620815b2f5848e Mon Sep 17 00:00:00 2001 From: James Carthew Date: Sun, 7 Apr 2024 12:11:19 +0930 Subject: [PATCH 04/26] Updated Debian Build. --- debian/changelog | 2 +- debian/compat | 2 +- debian/control | 4 ++-- debian/copyright | 2 +- debian/rules | 21 ++++++++++++++++++++- debian/source/format | 1 + meson.build | 3 --- 7 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 debian/source/format diff --git a/debian/changelog b/debian/changelog index 3ee2b598..cab1350f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -purple-facebook (0.0.0-1) UNRELEASED; urgency=medium +libpurple-facebook (0.9.7) UNRELEASED; urgency=medium * Initial debian support. diff --git a/debian/compat b/debian/compat index ec635144..48082f72 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -9 +12 diff --git a/debian/control b/debian/control index ba9c89dd..92997c28 100644 --- a/debian/control +++ b/debian/control @@ -1,4 +1,4 @@ -Source: purple-facebook +Source: libpurple-facebook Maintainer: jgeboski Section: misc Priority: optional @@ -6,7 +6,7 @@ Standards-Version: 3.9.6 Build-Depends: debhelper (>= 9), meson, libglib2.0-dev (>= 2.28), libjson-glib-dev (>= 0.14), libpurple-dev Homepage: https://github.com/jgeboski/purple-facebook -Package: purple-facebook +Package: libpurple-facebook Architecture: any Section: misc Priority: optional diff --git a/debian/copyright b/debian/copyright index 67cb7edc..ba26108c 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,5 +1,5 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: purple-facebook +Upstream-Name: libpurple-facebook Source: https://github.com/jgeboski/purple-facebook Files: * diff --git a/debian/rules b/debian/rules index 6b11501d..4b23fca6 100755 --- a/debian/rules +++ b/debian/rules @@ -1,4 +1,23 @@ #!/usr/bin/make -f +#%: +# dh build --buildsystem=meson + +# Define variables +PKG = libpurple-facebook +LIBRARY_NAME = libpurple-facebook.so + %: - dh build --buildsystem=meson + dh $@ + +override_dh_auto_configure: + dh_auto_configure --buildsystem=meson + +override_dh_auto_build: + dh_auto_build + +override_dh_auto_install: + dh_auto_install + +override_dh_auto_test: + dh_auto_test diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..89ae9db8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/meson.build b/meson.build index 36c293b4..85ab4121 100644 --- a/meson.build +++ b/meson.build @@ -84,10 +84,7 @@ else purple_plugindir = plugin_dir endif - - # Define library #plugin_library = shared_library('purple-facebook-plugin', source_files, dependencies : [glib_dep, json_dep, purple_dep, zlib_dep], c_args: compiler_options) plugin_library = shared_library('purple-facebook-plugin', source_files, dependencies : [glib_dep, json_dep, purple_dep, zlib_dep], c_args: compiler_options, install: true, install_dir: purple_plugindir) - From 39d80adee377b91780bfbc8524f7e1d7a99583cc Mon Sep 17 00:00:00 2001 From: James Carthew Date: Sun, 7 Apr 2024 13:15:29 +0930 Subject: [PATCH 05/26] Created Workflow for CI. --- .githhub/workflows/ci.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .githhub/workflows/ci.yaml diff --git a/.githhub/workflows/ci.yaml b/.githhub/workflows/ci.yaml new file mode 100644 index 00000000..089cd4ed --- /dev/null +++ b/.githhub/workflows/ci.yaml @@ -0,0 +1,22 @@ +name: CI + +on: + - pull_request + - push + +jobs: + build: + runs-on: debian-12 + + steps: + - uses: actions/checkout@v2 + - name: Dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y --no-install-recommends clang git make pkg-config meson libglib2.0-dev libjson-glib-dev libpurple-dev libpurple0 libjson-glib-1.0-0 libglib2.0-0 + + - uses: actions/cache@v2 + id: cache + with: + path: /tmp/purple-facebook/ + key: purple-facebook From 27cd07c99fee932f073661fed8c30a7856e07f1a Mon Sep 17 00:00:00 2001 From: DMJC Date: Sun, 7 Apr 2024 13:17:34 +0930 Subject: [PATCH 06/26] Create main.yml Create main.yml for CI --- .github/workflows/main.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..089cd4ed --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,22 @@ +name: CI + +on: + - pull_request + - push + +jobs: + build: + runs-on: debian-12 + + steps: + - uses: actions/checkout@v2 + - name: Dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y --no-install-recommends clang git make pkg-config meson libglib2.0-dev libjson-glib-dev libpurple-dev libpurple0 libjson-glib-1.0-0 libglib2.0-0 + + - uses: actions/cache@v2 + id: cache + with: + path: /tmp/purple-facebook/ + key: purple-facebook From 64a2e793308da4e51bece897b611e7550e155af5 Mon Sep 17 00:00:00 2001 From: DMJC Date: Sun, 7 Apr 2024 13:21:36 +0930 Subject: [PATCH 07/26] Delete .githhub directory --- .githhub/workflows/ci.yaml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .githhub/workflows/ci.yaml diff --git a/.githhub/workflows/ci.yaml b/.githhub/workflows/ci.yaml deleted file mode 100644 index 089cd4ed..00000000 --- a/.githhub/workflows/ci.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: CI - -on: - - pull_request - - push - -jobs: - build: - runs-on: debian-12 - - steps: - - uses: actions/checkout@v2 - - name: Dependencies - run: | - sudo apt-get update -y - sudo apt-get install -y --no-install-recommends clang git make pkg-config meson libglib2.0-dev libjson-glib-dev libpurple-dev libpurple0 libjson-glib-1.0-0 libglib2.0-0 - - - uses: actions/cache@v2 - id: cache - with: - path: /tmp/purple-facebook/ - key: purple-facebook From cba56a0ffd580110b1dc6446c44cbed9a0b75b30 Mon Sep 17 00:00:00 2001 From: DMJC Date: Sun, 7 Apr 2024 13:27:20 +0930 Subject: [PATCH 08/26] Update main.yml Updated to latest Ubuntu --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 089cd4ed..4f98cee9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,7 +6,7 @@ on: jobs: build: - runs-on: debian-12 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 From 1f65f531f3f13b0fd5d7ecd6cd8759202ad067fe Mon Sep 17 00:00:00 2001 From: DMJC Date: Sun, 7 Apr 2024 13:34:10 +0930 Subject: [PATCH 09/26] Update main.yml Added build commands --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4f98cee9..84a458b0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,6 +15,12 @@ jobs: sudo apt-get update -y sudo apt-get install -y --no-install-recommends clang git make pkg-config meson libglib2.0-dev libjson-glib-dev libpurple-dev libpurple0 libjson-glib-1.0-0 libglib2.0-0 + - name: make + run: | + meson setup build + cd build + ninja + - uses: actions/cache@v2 id: cache with: From 582f290e525ba109f8203fa0847cd4bf28b65aa9 Mon Sep 17 00:00:00 2001 From: DMJC Date: Sun, 7 Apr 2024 13:36:29 +0930 Subject: [PATCH 10/26] Update main.yml Added Archive Step --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84a458b0..3c6f0439 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,3 +26,9 @@ jobs: with: path: /tmp/purple-facebook/ key: purple-facebook + + - name: archive + uses: actions/upload-artifact@v3 + with: + name: plugin + path: lib*.so From 9bd73f239d07eff2e7edee42c122110b0a6ad93e Mon Sep 17 00:00:00 2001 From: DMJC Date: Sun, 7 Apr 2024 13:38:25 +0930 Subject: [PATCH 11/26] Update main.yml Added build folder to archive. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3c6f0439..656fcd5a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,4 +31,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: plugin - path: lib*.so + path: build/lib*.so From 08f92935e98aa5883a0a5f1e5a1e1c72a57a6e67 Mon Sep 17 00:00:00 2001 From: James Carthew Date: Sun, 7 Apr 2024 13:59:59 +0930 Subject: [PATCH 12/26] ignore build folder from github commits. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e69de29b..378eac25 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +build From 3f98c67a9fd68732a08be27b9fe89864f8b830ba Mon Sep 17 00:00:00 2001 From: James Carthew Date: Sun, 7 Apr 2024 14:04:40 +0930 Subject: [PATCH 13/26] Updated api.c whitespace and api descriptions to match bitlbee. --- pidgin/libpurple/protocols/facebook/api.c | 5470 ++++++++++----------- 1 file changed, 2735 insertions(+), 2735 deletions(-) diff --git a/pidgin/libpurple/protocols/facebook/api.c b/pidgin/libpurple/protocols/facebook/api.c index 920cb4db..1abc9381 100644 --- a/pidgin/libpurple/protocols/facebook/api.c +++ b/pidgin/libpurple/protocols/facebook/api.c @@ -36,45 +36,45 @@ typedef struct _FbApiData FbApiData; enum { - PROP_0, + PROP_0, - PROP_CID, - PROP_DID, - PROP_MID, - PROP_STOKEN, - PROP_TOKEN, - PROP_UID, + PROP_CID, + PROP_DID, + PROP_MID, + PROP_STOKEN, + PROP_TOKEN, + PROP_UID, - PROP_N + PROP_N }; struct _FbApiPrivate { - FbMqtt *mqtt; - FbHttpConns *cons; - PurpleConnection *gc; - GHashTable *data; - gboolean retrying; - - FbId uid; - gint64 sid; - guint64 mid; - gchar *cid; - gchar *did; - gchar *stoken; - gchar *token; - - GQueue *msgs; - gboolean invisible; - guint unread; - FbId lastmid; - gchar *contacts_delta; + FbMqtt *mqtt; + FbHttpConns *cons; + PurpleConnection *gc; + GHashTable *data; + gboolean retrying; + + FbId uid; + gint64 sid; + guint64 mid; + gchar *cid; + gchar *did; + gchar *stoken; + gchar *token; + + GQueue *msgs; + gboolean invisible; + guint unread; + FbId lastmid; + gchar *contacts_delta; }; struct _FbApiData { - gpointer data; - GDestroyNotify func; + gpointer data; + GDestroyNotify func; }; static void @@ -98,1341 +98,1341 @@ static void fb_api_set_property(GObject *obj, guint prop, const GValue *val, GParamSpec *pspec) { - FbApiPrivate *priv = FB_API(obj)->priv; - - switch (prop) { - case PROP_CID: - g_free(priv->cid); - priv->cid = g_value_dup_string(val); - break; - case PROP_DID: - g_free(priv->did); - priv->did = g_value_dup_string(val); - break; - case PROP_MID: - priv->mid = g_value_get_uint64(val); - break; - case PROP_STOKEN: - g_free(priv->stoken); - priv->stoken = g_value_dup_string(val); - break; - case PROP_TOKEN: - g_free(priv->token); - priv->token = g_value_dup_string(val); - break; - case PROP_UID: - priv->uid = g_value_get_int64(val); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); - break; - } + FbApiPrivate *priv = FB_API(obj)->priv; + + switch (prop) { + case PROP_CID: + g_free(priv->cid); + priv->cid = g_value_dup_string(val); + break; + case PROP_DID: + g_free(priv->did); + priv->did = g_value_dup_string(val); + break; + case PROP_MID: + priv->mid = g_value_get_uint64(val); + break; + case PROP_STOKEN: + g_free(priv->stoken); + priv->stoken = g_value_dup_string(val); + break; + case PROP_TOKEN: + g_free(priv->token); + priv->token = g_value_dup_string(val); + break; + case PROP_UID: + priv->uid = g_value_get_int64(val); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); + break; + } } static void fb_api_get_property(GObject *obj, guint prop, GValue *val, GParamSpec *pspec) { - FbApiPrivate *priv = FB_API(obj)->priv; - - switch (prop) { - case PROP_CID: - g_value_set_string(val, priv->cid); - break; - case PROP_DID: - g_value_set_string(val, priv->did); - break; - case PROP_MID: - g_value_set_uint64(val, priv->mid); - break; - case PROP_STOKEN: - g_value_set_string(val, priv->stoken); - break; - case PROP_TOKEN: - g_value_set_string(val, priv->token); - break; - case PROP_UID: - g_value_set_int64(val, priv->uid); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); - break; - } + FbApiPrivate *priv = FB_API(obj)->priv; + + switch (prop) { + case PROP_CID: + g_value_set_string(val, priv->cid); + break; + case PROP_DID: + g_value_set_string(val, priv->did); + break; + case PROP_MID: + g_value_set_uint64(val, priv->mid); + break; + case PROP_STOKEN: + g_value_set_string(val, priv->stoken); + break; + case PROP_TOKEN: + g_value_set_string(val, priv->token); + break; + case PROP_UID: + g_value_set_int64(val, priv->uid); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); + break; + } } static void fb_api_dispose(GObject *obj) { - FbApiData *fata; - FbApiPrivate *priv = FB_API(obj)->priv; - GHashTableIter iter; + FbApiData *fata; + FbApiPrivate *priv = FB_API(obj)->priv; + GHashTableIter iter; - fb_http_conns_cancel_all(priv->cons); - g_hash_table_iter_init(&iter, priv->data); + fb_http_conns_cancel_all(priv->cons); + g_hash_table_iter_init(&iter, priv->data); - while (g_hash_table_iter_next(&iter, NULL, (gpointer) &fata)) { - fata->func(fata->data); - g_free(fata); - } + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &fata)) { + fata->func(fata->data); + g_free(fata); + } - if (G_UNLIKELY(priv->mqtt != NULL)) { - g_object_unref(priv->mqtt); - } + if (G_UNLIKELY(priv->mqtt != NULL)) { + g_object_unref(priv->mqtt); + } - fb_http_conns_free(priv->cons); - g_hash_table_destroy(priv->data); - g_queue_free_full(priv->msgs, (GDestroyNotify) fb_api_message_free); + fb_http_conns_free(priv->cons); + g_hash_table_destroy(priv->data); + g_queue_free_full(priv->msgs, (GDestroyNotify) fb_api_message_free); - g_free(priv->cid); - g_free(priv->did); - g_free(priv->stoken); - g_free(priv->token); - g_free(priv->contacts_delta); + g_free(priv->cid); + g_free(priv->did); + g_free(priv->stoken); + g_free(priv->token); + g_free(priv->contacts_delta); } static void fb_api_class_init(FbApiClass *klass) { - GObjectClass *gklass = G_OBJECT_CLASS(klass); - GParamSpec *props[PROP_N] = {NULL}; - - gklass->set_property = fb_api_set_property; - gklass->get_property = fb_api_get_property; - gklass->dispose = fb_api_dispose; - g_type_class_add_private(klass, sizeof (FbApiPrivate)); - - /** - * FbApi:cid: - * - * The client identifier for MQTT. This value should be saved - * and loaded for persistence. - */ - props[PROP_CID] = g_param_spec_string( - "cid", - "Client ID", - "Client identifier for MQTT", - NULL, - G_PARAM_READWRITE); - - /** - * FbApi:did: - * - * The device identifier for the MQTT message queue. This value - * should be saved and loaded for persistence. - */ - props[PROP_DID] = g_param_spec_string( - "did", - "Device ID", - "Device identifier for the MQTT message queue", - NULL, - G_PARAM_READWRITE); - - /** - * FbApi:mid: - * - * The MQTT identifier. This value should be saved and loaded - * for persistence. - */ - props[PROP_MID] = g_param_spec_uint64( - "mid", - "MQTT ID", - "MQTT identifier", - 0, G_MAXUINT64, 0, - G_PARAM_READWRITE); - - /** - * FbApi:stoken: - * - * The synchronization token for the MQTT message queue. This - * value should be saved and loaded for persistence. - */ - props[PROP_STOKEN] = g_param_spec_string( - "stoken", - "Sync Token", - "Synchronization token for the MQTT message queue", - NULL, - G_PARAM_READWRITE); - - /** - * FbApi:token: - * - * The access token for authentication. This value should be - * saved and loaded for persistence. - */ - props[PROP_TOKEN] = g_param_spec_string( - "token", - "Access Token", - "Access token for authentication", - NULL, - G_PARAM_READWRITE); - - /** - * FbApi:uid: - * - * The #FbId of the user of the #FbApi. - */ - props[PROP_UID] = g_param_spec_int64( - "uid", - "User ID", - "User identifier", - 0, G_MAXINT64, 0, - G_PARAM_READWRITE); - g_object_class_install_properties(gklass, PROP_N, props); - - /** - * FbApi::auth: - * @api: The #FbApi. - * - * Emitted upon the successful completion of the authentication - * process. This is emitted as a result of #fb_api_auth(). - */ - g_signal_new("auth", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__VOID, - G_TYPE_NONE, - 0); - - /** - * FbApi::connect: - * @api: The #FbApi. - * - * Emitted upon the successful completion of the connection - * process. This is emitted as a result of #fb_api_connect(). - */ - g_signal_new("connect", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__VOID, - G_TYPE_NONE, - 0); - - /** - * FbApi::contact: - * @api: The #FbApi. - * @user: The #FbApiUser. - * - * Emitted upon the successful reply of a contact request. This - * is emitted as a result of #fb_api_contact(). - */ - g_signal_new("contact", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::contacts: - * @api: The #FbApi. - * @users: The #GSList of #FbApiUser's. - * @complete: #TRUE if the list is fetched, otherwise #FALSE. - * - * Emitted upon the successful reply of a contacts request. - * This is emitted as a result of #fb_api_contacts(). This can - * be emitted multiple times before the entire contacts list - * has been fetched. Use @complete for detecting the completion - * status of the list fetch. - */ - g_signal_new("contacts", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER_BOOLEAN, - G_TYPE_NONE, - 2, G_TYPE_POINTER, G_TYPE_BOOLEAN); - - /** - * FbApi::contacts-delta: - * @api: The #FbApi. - * @added: The #GSList of added #FbApiUser's. - * @removed: The #GSList of strings with removed user ids. - * - * Like 'contacts', but only the deltas. - */ - g_signal_new("contacts-delta", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER_POINTER, - G_TYPE_NONE, - 2, G_TYPE_POINTER, G_TYPE_POINTER); - - /** - * FbApi::error: - * @api: The #FbApi. - * @error: The #GError. - * - * Emitted whenever an error is hit within the #FbApi. This - * should disconnect the #FbApi with #fb_api_disconnect(). - */ - g_signal_new("error", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::events: - * @api: The #FbApi. - * @events: The #GSList of #FbApiEvent's. - * - * Emitted upon incoming events from the stream. - */ - g_signal_new("events", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::messages: - * @api: The #FbApi. - * @msgs: The #GSList of #FbApiMessage's. - * - * Emitted upon incoming messages from the stream. - */ - g_signal_new("messages", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::presences: - * @api: The #FbApi. - * @press: The #GSList of #FbApiPresence's. - * - * Emitted upon incoming presences from the stream. - */ - g_signal_new("presences", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::thread: - * @api: The #FbApi. - * @thrd: The #FbApiThread. - * - * Emitted upon the successful reply of a thread request. This - * is emitted as a result of #fb_api_thread(). - */ - g_signal_new("thread", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::thread-create: - * @api: The #FbApi. - * @tid: The thread #FbId. - * - * Emitted upon the successful reply of a thread creation - * request. This is emitted as a result of - * #fb_api_thread_create(). - */ - g_signal_new("thread-create", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__INT64, - G_TYPE_NONE, - 1, FB_TYPE_ID); - - /** - * FbApi::thread-kicked: - * @api: The #FbApi. - * @thrd: The #FbApiThread. - * - * Emitted upon the reply of a thread request when the user is no longer - * part of that thread. This is emitted as a result of #fb_api_thread(). - */ - g_signal_new("thread-kicked", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::threads: - * @api: The #FbApi. - * @thrds: The #GSList of #FbApiThread's. - * - * Emitted upon the successful reply of a threads request. This - * is emitted as a result of #fb_api_threads(). - */ - g_signal_new("threads", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * FbApi::typing: - * @api: The #FbApi. - * @typg: The #FbApiTyping. - * - * Emitted upon an incoming typing state from the stream. - */ - g_signal_new("typing", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_ACTION, - 0, - NULL, NULL, - fb_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); + GObjectClass *gklass = G_OBJECT_CLASS(klass); + GParamSpec *props[PROP_N] = {NULL}; + + gklass->set_property = fb_api_set_property; + gklass->get_property = fb_api_get_property; + gklass->dispose = fb_api_dispose; + g_type_class_add_private(klass, sizeof (FbApiPrivate)); + + /** + * FbApi:cid: + * + * The client identifier for MQTT. This value should be saved + * and loaded for persistence. + */ + props[PROP_CID] = g_param_spec_string( + "cid", + "Client ID", + "Client identifier for MQTT", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:did: + * + * The device identifier for the MQTT message queue. This value + * should be saved and loaded for persistence. + */ + props[PROP_DID] = g_param_spec_string( + "did", + "Device ID", + "Device identifier for the MQTT message queue", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:mid: + * + * The MQTT identifier. This value should be saved and loaded + * for persistence. + */ + props[PROP_MID] = g_param_spec_uint64( + "mid", + "MQTT ID", + "MQTT identifier", + 0, G_MAXUINT64, 0, + G_PARAM_READWRITE); + + /** + * FbApi:stoken: + * + * The synchronization token for the MQTT message queue. This + * value should be saved and loaded for persistence. + */ + props[PROP_STOKEN] = g_param_spec_string( + "stoken", + "Sync Token", + "Synchronization token for the MQTT message queue", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:token: + * + * The access token for authentication. This value should be + * saved and loaded for persistence. + */ + props[PROP_TOKEN] = g_param_spec_string( + "token", + "Access Token", + "Access token for authentication", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:uid: + * + * The #FbId of the user of the #FbApi. + */ + props[PROP_UID] = g_param_spec_int64( + "uid", + "User ID", + "User identifier", + 0, G_MAXINT64, 0, + G_PARAM_READWRITE); + g_object_class_install_properties(gklass, PROP_N, props); + + /** + * FbApi::auth: + * @api: The #FbApi. + * + * Emitted upon the successful completion of the authentication + * process. This is emitted as a result of #fb_api_auth(). + */ + g_signal_new("auth", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * FbApi::connect: + * @api: The #FbApi. + * + * Emitted upon the successful completion of the connection + * process. This is emitted as a result of #fb_api_connect(). + */ + g_signal_new("connect", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * FbApi::contact: + * @api: The #FbApi. + * @user: The #FbApiUser. + * + * Emitted upon the successful reply of a contact request. This + * is emitted as a result of #fb_api_contact(). + */ + g_signal_new("contact", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::contacts: + * @api: The #FbApi. + * @users: The #GSList of #FbApiUser's. + * @complete: #TRUE if the list is fetched, otherwise #FALSE. + * + * Emitted upon the successful reply of a contacts request. + * This is emitted as a result of #fb_api_contacts(). This can + * be emitted multiple times before the entire contacts list + * has been fetched. Use @complete for detecting the completion + * status of the list fetch. + */ + g_signal_new("contacts", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER_BOOLEAN, + G_TYPE_NONE, + 2, G_TYPE_POINTER, G_TYPE_BOOLEAN); + + /** + * FbApi::contacts-delta: + * @api: The #FbApi. + * @added: The #GSList of added #FbApiUser's. + * @removed: The #GSList of strings with removed user ids. + * + * Like 'contacts', but only the deltas. + */ + g_signal_new("contacts-delta", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER_POINTER, + G_TYPE_NONE, + 2, G_TYPE_POINTER, G_TYPE_POINTER); + + /** + * FbApi::error: + * @api: The #FbApi. + * @error: The #GError. + * + * Emitted whenever an error is hit within the #FbApi. This + * should disconnect the #FbApi with #fb_api_disconnect(). + */ + g_signal_new("error", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::events: + * @api: The #FbApi. + * @events: The #GSList of #FbApiEvent's. + * + * Emitted upon incoming events from the stream. + */ + g_signal_new("events", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::messages: + * @api: The #FbApi. + * @msgs: The #GSList of #FbApiMessage's. + * + * Emitted upon incoming messages from the stream. + */ + g_signal_new("messages", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::presences: + * @api: The #FbApi. + * @press: The #GSList of #FbApiPresence's. + * + * Emitted upon incoming presences from the stream. + */ + g_signal_new("presences", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::thread: + * @api: The #FbApi. + * @thrd: The #FbApiThread. + * + * Emitted upon the successful reply of a thread request. This + * is emitted as a result of #fb_api_thread(). + */ + g_signal_new("thread", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::thread-create: + * @api: The #FbApi. + * @tid: The thread #FbId. + * + * Emitted upon the successful reply of a thread creation + * request. This is emitted as a result of + * #fb_api_thread_create(). + */ + g_signal_new("thread-create", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__INT64, + G_TYPE_NONE, + 1, FB_TYPE_ID); + + /** + * FbApi::thread-kicked: + * @api: The #FbApi. + * @thrd: The #FbApiThread. + * + * Emitted upon the reply of a thread request when the user is no longer + * part of that thread. This is emitted as a result of #fb_api_thread(). + */ + g_signal_new("thread-kicked", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::threads: + * @api: The #FbApi. + * @thrds: The #GSList of #FbApiThread's. + * + * Emitted upon the successful reply of a threads request. This + * is emitted as a result of #fb_api_threads(). + */ + g_signal_new("threads", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::typing: + * @api: The #FbApi. + * @typg: The #FbApiTyping. + * + * Emitted upon an incoming typing state from the stream. + */ + g_signal_new("typing", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); } static void fb_api_init(FbApi *api) { - FbApiPrivate *priv; + FbApiPrivate *priv; - priv = G_TYPE_INSTANCE_GET_PRIVATE(api, FB_TYPE_API, FbApiPrivate); - api->priv = priv; + priv = G_TYPE_INSTANCE_GET_PRIVATE(api, FB_TYPE_API, FbApiPrivate); + api->priv = priv; - priv->cons = fb_http_conns_new(); - priv->msgs = g_queue_new(); - priv->data = g_hash_table_new_full(g_direct_hash, g_direct_equal, - NULL, NULL); + priv->cons = fb_http_conns_new(); + priv->msgs = g_queue_new(); + priv->data = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, NULL); } GQuark fb_api_error_quark(void) { - static GQuark q = 0; + static GQuark q = 0; - if (G_UNLIKELY(q == 0)) { - q = g_quark_from_static_string("fb-api-error-quark"); - } + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-api-error-quark"); + } - return q; + return q; } static void fb_api_data_set(FbApi *api, gpointer handle, gpointer data, GDestroyNotify func) { - FbApiData *fata; - FbApiPrivate *priv = api->priv; + FbApiData *fata; + FbApiPrivate *priv = api->priv; - fata = g_new0(FbApiData, 1); - fata->data = data; - fata->func = func; - g_hash_table_replace(priv->data, handle, fata); + fata = g_new0(FbApiData, 1); + fata->data = data; + fata->func = func; + g_hash_table_replace(priv->data, handle, fata); } static gpointer fb_api_data_take(FbApi *api, gconstpointer handle) { - FbApiData *fata; - FbApiPrivate *priv = api->priv; - gpointer data; + FbApiData *fata; + FbApiPrivate *priv = api->priv; + gpointer data; - fata = g_hash_table_lookup(priv->data, handle); + fata = g_hash_table_lookup(priv->data, handle); - if (fata == NULL) { - return NULL; - } + if (fata == NULL) { + return NULL; + } - data = fata->data; - g_hash_table_remove(priv->data, handle); - g_free(fata); - return data; + data = fata->data; + g_hash_table_remove(priv->data, handle); + g_free(fata); + return data; } static gboolean fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node) { - const gchar *str; - FbApiError errc = FB_API_ERROR_GENERAL; - FbApiPrivate *priv; - FbJsonValues *values; - gboolean success = TRUE; - gchar *msg; - GError *err = NULL; - gint64 code; - guint i; - JsonNode *root; - - static const gchar *exprs[] = { - "$.error.message", - "$.error.summary", - "$.error_msg", - "$.errorCode", - "$.failedSend.errorMessage", - }; - - g_return_val_if_fail(FB_IS_API(api), FALSE); - priv = api->priv; - - if (G_UNLIKELY(size == 0)) { - fb_api_error(api, FB_API_ERROR_GENERAL, _("Empty JSON data")); - return FALSE; - } - - fb_util_debug(FB_UTIL_DEBUG_INFO, "Parsing JSON: %.*s\n", - (gint) size, (const gchar *) data); - - root = fb_json_node_new(data, size, &err); - FB_API_ERROR_EMIT(api, err, return FALSE); - - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.error_code"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.error.type"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.errorCode"); - fb_json_values_update(values, &err); - - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return FALSE - ); - - code = fb_json_values_next_int(values, 0); - str = fb_json_values_next_str(values, NULL); - - if (purple_strequal(str, "OAuthException") || (code == 401)) { - errc = FB_API_ERROR_AUTH; - success = FALSE; - - g_free(priv->stoken); - priv->stoken = NULL; - - g_free(priv->token); - priv->token = NULL; - } - - /* 509 is used for "invalid attachment id" */ - if (code == 509) { - errc = FB_API_ERROR_NONFATAL; - success = FALSE; - } - - str = fb_json_values_next_str(values, NULL); - - if (purple_strequal(str, "ERROR_QUEUE_NOT_FOUND") || - purple_strequal(str, "ERROR_QUEUE_LOST")) - { - errc = FB_API_ERROR_QUEUE; - success = FALSE; - - g_free(priv->stoken); - priv->stoken = NULL; - } - - g_object_unref(values); - - for (msg = NULL, i = 0; i < G_N_ELEMENTS(exprs); i++) { - msg = fb_json_node_get_str(root, exprs[i], NULL); - - if (msg != NULL) { - success = FALSE; - break; - } - } - - if (!success && (msg == NULL)) { - msg = g_strdup(_("Unknown error")); - } - - if (msg != NULL) { - fb_api_error(api, errc, "%s", msg); - json_node_free(root); - g_free(msg); - return FALSE; - } - - if (node != NULL) { - *node = root; - } else { - json_node_free(root); - } - - return TRUE; + const gchar *str; + FbApiError errc = FB_API_ERROR_GENERAL; + FbApiPrivate *priv; + FbJsonValues *values; + gboolean success = TRUE; + gchar *msg; + GError *err = NULL; + gint64 code; + guint i; + JsonNode *root; + + static const gchar *exprs[] = { + "$.error.message", + "$.error.summary", + "$.error_msg", + "$.errorCode", + "$.failedSend.errorMessage", + }; + + g_return_val_if_fail(FB_IS_API(api), FALSE); + priv = api->priv; + + if (G_UNLIKELY(size == 0)) { + fb_api_error(api, FB_API_ERROR_GENERAL, _("Empty JSON data")); + return FALSE; + } + + fb_util_debug(FB_UTIL_DEBUG_INFO, "Parsing JSON: %.*s\n", + (gint) size, (const gchar *) data); + + root = fb_json_node_new(data, size, &err); + FB_API_ERROR_EMIT(api, err, return FALSE); + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.error_code"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.error.type"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.errorCode"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return FALSE + ); + + code = fb_json_values_next_int(values, 0); + str = fb_json_values_next_str(values, NULL); + + if (purple_strequal(str, "OAuthException") || (code == 401)) { + errc = FB_API_ERROR_AUTH; + success = FALSE; + + g_free(priv->stoken); + priv->stoken = NULL; + + g_free(priv->token); + priv->token = NULL; + } + + /* 509 is used for "invalid attachment id" */ + if (code == 509) { + errc = FB_API_ERROR_NONFATAL; + success = FALSE; + } + + str = fb_json_values_next_str(values, NULL); + + if (purple_strequal(str, "ERROR_QUEUE_NOT_FOUND") || + purple_strequal(str, "ERROR_QUEUE_LOST")) + { + errc = FB_API_ERROR_QUEUE; + success = FALSE; + + g_free(priv->stoken); + priv->stoken = NULL; + } + + g_object_unref(values); + + for (msg = NULL, i = 0; i < G_N_ELEMENTS(exprs); i++) { + msg = fb_json_node_get_str(root, exprs[i], NULL); + + if (msg != NULL) { + success = FALSE; + break; + } + } + + if (!success && (msg == NULL)) { + msg = g_strdup(_("Unknown error")); + } + + if (msg != NULL) { + fb_api_error(api, errc, "%s", msg); + json_node_free(root); + g_free(msg); + return FALSE; + } + + if (node != NULL) { + *node = root; + } else { + json_node_free(root); + } + + return TRUE; } static gboolean fb_api_http_chk(FbApi *api, PurpleHttpConnection *con, PurpleHttpResponse *res, JsonNode **root) { - const gchar *data; - const gchar *msg; - FbApiPrivate *priv = api->priv; - gchar *emsg; - GError *err = NULL; - gint code; - gsize size; + const gchar *data; + const gchar *msg; + FbApiPrivate *priv = api->priv; + gchar *emsg; + GError *err = NULL; + gint code; + gsize size; - if (fb_http_conns_is_canceled(priv->cons)) { - return FALSE; - } + if (fb_http_conns_is_canceled(priv->cons)) { + return FALSE; + } - msg = purple_http_response_get_error(res); - code = purple_http_response_get_code(res); - data = purple_http_response_get_data(res, &size); - fb_http_conns_remove(priv->cons, con); + msg = purple_http_response_get_error(res); + code = purple_http_response_get_code(res); + data = purple_http_response_get_data(res, &size); + fb_http_conns_remove(priv->cons, con); - if (msg != NULL) { - emsg = g_strdup_printf("%s (%d)", msg, code); - } else { - emsg = g_strdup_printf("%d", code); - } + if (msg != NULL) { + emsg = g_strdup_printf("%s (%d)", msg, code); + } else { + emsg = g_strdup_printf("%d", code); + } - fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Response (%p):", con); - fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Error: %s", emsg); - g_free(emsg); + fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Response (%p):", con); + fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Error: %s", emsg); + g_free(emsg); - if (G_LIKELY(size > 0)) { - fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Data: %.*s", - (gint) size, data); - } + if (G_LIKELY(size > 0)) { + fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Data: %.*s", + (gint) size, data); + } - if (fb_http_error_chk(res, &err) && (root == NULL)) { - return TRUE; - } + if (fb_http_error_chk(res, &err) && (root == NULL)) { + return TRUE; + } - /* Rudimentary check to prevent wrongful error parsing */ - if ((size < 2) || (data[0] != '{') || (data[size - 1] != '}')) { - FB_API_ERROR_EMIT(api, err, return FALSE); - } + /* Rudimentary check to prevent wrongful error parsing */ + if ((size < 2) || (data[0] != '{') || (data[size - 1] != '}')) { + FB_API_ERROR_EMIT(api, err, return FALSE); + } - if (!fb_api_json_chk(api, data, size, root)) { - if (G_UNLIKELY(err != NULL)) { - g_error_free(err); - } + if (!fb_api_json_chk(api, data, size, root)) { + if (G_UNLIKELY(err != NULL)) { + g_error_free(err); + } - return FALSE; - } + return FALSE; + } - FB_API_ERROR_EMIT(api, err, return FALSE); - return TRUE; + FB_API_ERROR_EMIT(api, err, return FALSE); + return TRUE; } static PurpleHttpConnection * fb_api_http_req(FbApi *api, const gchar *url, const gchar *name, const gchar *method, FbHttpParams *params, - PurpleHttpCallback callback) -{ - FbApiPrivate *priv = api->priv; - gchar *data; - gchar *key; - gchar *val; - GList *keys; - GList *l; - GString *gstr; - PurpleHttpConnection *ret; - PurpleHttpRequest *req; - - fb_http_params_set_str(params, "api_key", FB_API_KEY); - fb_http_params_set_str(params, "device_id", priv->did); - fb_http_params_set_str(params, "fb_api_req_friendly_name", name); - fb_http_params_set_str(params, "format", "json"); - fb_http_params_set_str(params, "method", method); - - val = fb_util_get_locale(); - fb_http_params_set_str(params, "locale", val); - g_free(val); - - req = purple_http_request_new(url); - purple_http_request_set_max_len(req, -1); - purple_http_request_set_method(req, "POST"); - - /* Ensure an old signature is not computed */ - g_hash_table_remove(params, "sig"); - - gstr = g_string_new(NULL); - keys = g_hash_table_get_keys(params); - keys = g_list_sort(keys, (GCompareFunc) g_ascii_strcasecmp); - - for (l = keys; l != NULL; l = l->next) { - key = l->data; - val = g_hash_table_lookup(params, key); - g_string_append_printf(gstr, "%s=%s", key, val); - } - - g_string_append(gstr, FB_API_SECRET); - data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, - gstr->len); - fb_http_params_set_str(params, "sig", data); - g_string_free(gstr, TRUE); - g_list_free(keys); - g_free(data); - - if (priv->token != NULL) { - data = g_strdup_printf("OAuth %s", priv->token); - purple_http_request_header_set(req, "Authorization", data); - g_free(data); - } - - purple_http_request_header_set(req, "User-Agent", FB_API_AGENT); - purple_http_request_header_set(req, "Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); - - data = fb_http_params_close(params, NULL); - purple_http_request_set_contents(req, data, -1); - ret = purple_http_request(priv->gc, req, callback, api); - fb_http_conns_add(priv->cons, ret); - purple_http_request_unref(req); - - fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Request (%p):", ret); - fb_util_debug(FB_UTIL_DEBUG_INFO, " Request URL: %s", url); - fb_util_debug(FB_UTIL_DEBUG_INFO, " Request Data: %s", data); - - g_free(data); - return ret; + PurpleHttpCallback callback) +{ + FbApiPrivate *priv = api->priv; + gchar *data; + gchar *key; + gchar *val; + GList *keys; + GList *l; + GString *gstr; + PurpleHttpConnection *ret; + PurpleHttpRequest *req; + + fb_http_params_set_str(params, "api_key", FB_API_KEY); + fb_http_params_set_str(params, "device_id", priv->did); + fb_http_params_set_str(params, "fb_api_req_friendly_name", name); + fb_http_params_set_str(params, "format", "json"); + fb_http_params_set_str(params, "method", method); + + val = fb_util_get_locale(); + fb_http_params_set_str(params, "locale", val); + g_free(val); + + req = purple_http_request_new(url); + purple_http_request_set_max_len(req, -1); + purple_http_request_set_method(req, "POST"); + + /* Ensure an old signature is not computed */ + g_hash_table_remove(params, "sig"); + + gstr = g_string_new(NULL); + keys = g_hash_table_get_keys(params); + keys = g_list_sort(keys, (GCompareFunc) g_ascii_strcasecmp); + + for (l = keys; l != NULL; l = l->next) { + key = l->data; + val = g_hash_table_lookup(params, key); + g_string_append_printf(gstr, "%s=%s", key, val); + } + + g_string_append(gstr, FB_API_SECRET); + data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, + gstr->len); + fb_http_params_set_str(params, "sig", data); + g_string_free(gstr, TRUE); + g_list_free(keys); + g_free(data); + + if (priv->token != NULL) { + data = g_strdup_printf("OAuth %s", priv->token); + purple_http_request_header_set(req, "Authorization", data); + g_free(data); + } + + purple_http_request_header_set(req, "User-Agent", FB_API_AGENT); + purple_http_request_header_set(req, "Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); + + data = fb_http_params_close(params, NULL); + purple_http_request_set_contents(req, data, -1); + ret = purple_http_request(priv->gc, req, callback, api); + fb_http_conns_add(priv->cons, ret); + purple_http_request_unref(req); + + fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Request (%p):", ret); + fb_util_debug(FB_UTIL_DEBUG_INFO, " Request URL: %s", url); + fb_util_debug(FB_UTIL_DEBUG_INFO, " Request Data: %s", data); + + g_free(data); + return ret; } static PurpleHttpConnection * fb_api_http_query(FbApi *api, gint64 query, JsonBuilder *builder, PurpleHttpCallback hcb) { - const gchar *name; - FbHttpParams *prms; - gchar *json; - - switch (query) { - case FB_API_QUERY_CONTACT: - name = "UsersQuery"; - break; - case FB_API_QUERY_CONTACTS: - name = "FetchContactsFullQuery"; - break; - case FB_API_QUERY_CONTACTS_AFTER: - name = "FetchContactsFullWithAfterQuery"; - break; - case FB_API_QUERY_CONTACTS_DELTA: - name = "FetchContactsDeltaQuery"; - break; - case FB_API_QUERY_STICKER: - name = "FetchStickersWithPreviewsQuery"; - break; - case FB_API_QUERY_THREAD: - name = "ThreadQuery"; - break; - case FB_API_QUERY_SEQ_ID: - case FB_API_QUERY_THREADS: - name = "ThreadListQuery"; - break; - case FB_API_QUERY_XMA: - name = "XMAQuery"; - break; - default: - g_return_val_if_reached(NULL); - return NULL; - } - - prms = fb_http_params_new(); - json = fb_json_bldr_close(builder, JSON_NODE_OBJECT, NULL); - - fb_http_params_set_strf(prms, "query_id", "%" G_GINT64_FORMAT, query); - fb_http_params_set_str(prms, "query_params", json); - g_free(json); - - return fb_api_http_req(api, FB_API_URL_GQL, name, "get", prms, hcb); + const gchar *name; + FbHttpParams *prms; + gchar *json; + + switch (query) { + case FB_API_QUERY_CONTACT: + name = "UsersQuery"; + break; + case FB_API_QUERY_CONTACTS: + name = "FetchContactsFullQuery"; + break; + case FB_API_QUERY_CONTACTS_AFTER: + name = "FetchContactsFullWithAfterQuery"; + break; + case FB_API_QUERY_CONTACTS_DELTA: + name = "FetchContactsDeltaQuery"; + break; + case FB_API_QUERY_STICKER: + name = "FetchStickersWithPreviewsQuery"; + break; + case FB_API_QUERY_THREAD: + name = "ThreadQuery"; + break; + case FB_API_QUERY_SEQ_ID: + case FB_API_QUERY_THREADS: + name = "ThreadListQuery"; + break; + case FB_API_QUERY_XMA: + name = "XMAQuery"; + break; + default: + g_return_val_if_reached(NULL); + return NULL; + } + + prms = fb_http_params_new(); + json = fb_json_bldr_close(builder, JSON_NODE_OBJECT, NULL); + + fb_http_params_set_strf(prms, "query_id", "%" G_GINT64_FORMAT, query); + fb_http_params_set_str(prms, "query_params", json); + g_free(json); + + return fb_api_http_req(api, FB_API_URL_GQL, name, "get", prms, hcb); } static void fb_api_cb_http_bool(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - const gchar *hata; - FbApi *api = data; + const gchar *hata; + FbApi *api = data; - if (!fb_api_http_chk(api, con, res, NULL)) { - return; - } + if (!fb_api_http_chk(api, con, res, NULL)) { + return; + } - hata = purple_http_response_get_data(res, NULL); + hata = purple_http_response_get_data(res, NULL); - if (!purple_strequal(hata, "true")) { - fb_api_error(api, FB_API_ERROR, - _("Failed generic API operation")); - } + if (!purple_strequal(hata, "true")) { + fb_api_error(api, FB_API_ERROR, + _("Failed generic API operation")); + } } static void fb_api_cb_mqtt_error(FbMqtt *mqtt, GError *error, gpointer data) { - FbApi *api = data; - FbApiPrivate *priv = api->priv; + FbApi *api = data; + FbApiPrivate *priv = api->priv; - if (!priv->retrying) { - priv->retrying = TRUE; - fb_util_debug_info("Attempting to reconnect the MQTT stream..."); - fb_api_connect(api, priv->invisible); - } else { - g_signal_emit_by_name(api, "error", error); - } + if (!priv->retrying) { + priv->retrying = TRUE; + fb_util_debug_info("Attempting to reconnect the MQTT stream..."); + fb_api_connect(api, priv->invisible); + } else { + g_signal_emit_by_name(api, "error", error); + } } static void fb_api_cb_mqtt_open(FbMqtt *mqtt, gpointer data) { - const GByteArray *bytes; - FbApi *api = data; - FbApiPrivate *priv = api->priv; - FbThrift *thft; - GByteArray *cytes; - GError *err = NULL; + const GByteArray *bytes; + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbThrift *thft; + GByteArray *cytes; + GError *err = NULL; - static guint8 flags = FB_MQTT_CONNECT_FLAG_USER | - FB_MQTT_CONNECT_FLAG_PASS | - FB_MQTT_CONNECT_FLAG_CLR; + static guint8 flags = FB_MQTT_CONNECT_FLAG_USER | + FB_MQTT_CONNECT_FLAG_PASS | + FB_MQTT_CONNECT_FLAG_CLR; - thft = fb_thrift_new(NULL, 0); + thft = fb_thrift_new(NULL, 0); - /* Write the client identifier */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 1, 0); - fb_thrift_write_str(thft, priv->cid); + /* Write the client identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 1, 0); + fb_thrift_write_str(thft, priv->cid); - fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRUCT, 4, 1); + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRUCT, 4, 1); - /* Write the user identifier */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 1, 0); - fb_thrift_write_i64(thft, priv->uid); + /* Write the user identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 1, 0); + fb_thrift_write_i64(thft, priv->uid); - /* Write the information string */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 2, 1); - fb_thrift_write_str(thft, FB_API_MQTT_AGENT); + /* Write the information string */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 2, 1); + fb_thrift_write_str(thft, FB_API_MQTT_AGENT); - /* Write the UNKNOWN ("cp"?) */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 3, 2); - fb_thrift_write_i64(thft, 23); + /* Write the client capabilities */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 3, 2); + fb_thrift_write_i64(thft, 23); - /* Write the UNKNOWN ("ecp"?) */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 4, 3); - fb_thrift_write_i64(thft, 26); + /* Write the endpoint capabilitites */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 4, 3); + fb_thrift_write_i64(thft, 26); - /* Write the UNKNOWN */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 5, 4); - fb_thrift_write_i32(thft, 1); + /* Write the publish payload format (deflate) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 5, 4); + fb_thrift_write_i32(thft, 1); - /* Write the UNKNOWN ("no_auto_fg"?) */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 6, 5); - fb_thrift_write_bool(thft, TRUE); + /* Write the noAutomaticForeground flag */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 6, 5); + fb_thrift_write_bool(thft, TRUE); - /* Write the visibility state */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 7, 6); - fb_thrift_write_bool(thft, !priv->invisible); + /* Write the visibility state */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 7, 6); + fb_thrift_write_bool(thft, !priv->invisible); - /* Write the device identifier */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 8, 7); - fb_thrift_write_str(thft, priv->did); + /* Write the device identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 8, 7); + fb_thrift_write_str(thft, priv->did); - /* Write the UNKNOWN ("fg"?) */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 9, 8); - fb_thrift_write_bool(thft, TRUE); + /* Write the isInitiallyForeground flag */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 9, 8); + fb_thrift_write_bool(thft, TRUE); - /* Write the UNKNOWN ("nwt"?) */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 10, 9); - fb_thrift_write_i32(thft, 1); + /* Write the network type (WIFI) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 10, 9); + fb_thrift_write_i32(thft, 1); - /* Write the UNKNOWN ("nwst"?) */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 11, 10); - fb_thrift_write_i32(thft, 0); + /* Write the network subtype (none) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 11, 10); + fb_thrift_write_i32(thft, 0); - /* Write the MQTT identifier */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 12, 11); - fb_thrift_write_i64(thft, priv->mid); + /* Write the MQTT identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 12, 11); + fb_thrift_write_i64(thft, priv->mid); - /* Write the UNKNOWN */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_LIST, 14, 12); - fb_thrift_write_list(thft, FB_THRIFT_TYPE_I32, 0); - fb_thrift_write_stop(thft); + /* Write the list of topics to subscribe */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_LIST, 14, 12); + fb_thrift_write_list(thft, FB_THRIFT_TYPE_I32, 0); + fb_thrift_write_stop(thft); - /* Write the token */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 15, 14); - fb_thrift_write_str(thft, priv->token); + /* Write the token */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 15, 14); + fb_thrift_write_str(thft, priv->token); - /* Write the STOP for the struct */ - fb_thrift_write_stop(thft); + /* Write the STOP for the struct */ + fb_thrift_write_stop(thft); - bytes = fb_thrift_get_bytes(thft); - cytes = fb_util_zlib_deflate(bytes, &err); + bytes = fb_thrift_get_bytes(thft); + cytes = fb_util_zlib_deflate(bytes, &err); - FB_API_ERROR_EMIT(api, err, - g_object_unref(thft); - return; - ); + FB_API_ERROR_EMIT(api, err, + g_object_unref(thft); + return; + ); - fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, "Writing connect"); - fb_mqtt_connect(mqtt, flags, cytes); + fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, "Writing connect"); + fb_mqtt_connect(mqtt, flags, cytes); - g_byte_array_free(cytes, TRUE); - g_object_unref(thft); + g_byte_array_free(cytes, TRUE); + g_object_unref(thft); } static void fb_api_connect_queue(FbApi *api) { - FbApiMessage *msg; - FbApiPrivate *priv = api->priv; - gchar *json; - JsonBuilder *bldr; - - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_add_int(bldr, "delta_batch_size", 125); - fb_json_bldr_add_int(bldr, "max_deltas_able_to_process", 1250); - fb_json_bldr_add_int(bldr, "sync_api_version", 3); - fb_json_bldr_add_str(bldr, "encoding", "JSON"); - - if (priv->stoken == NULL) { - fb_json_bldr_add_int(bldr, "initial_titan_sequence_id", - priv->sid); - fb_json_bldr_add_str(bldr, "device_id", priv->did); - fb_json_bldr_add_int(bldr, "entity_fbid", priv->uid); - - fb_json_bldr_obj_begin(bldr, "queue_params"); - fb_json_bldr_add_str(bldr, "buzz_on_deltas_enabled", "false"); - - fb_json_bldr_obj_begin(bldr, "graphql_query_hashes"); - fb_json_bldr_add_str(bldr, "xma_query_id", - G_STRINGIFY(FB_API_QUERY_XMA)); - fb_json_bldr_obj_end(bldr); - - fb_json_bldr_obj_begin(bldr, "graphql_query_params"); - fb_json_bldr_obj_begin(bldr, G_STRINGIFY(FB_API_QUERY_XMA)); - fb_json_bldr_add_str(bldr, "xma_id", ""); - fb_json_bldr_obj_end(bldr); - fb_json_bldr_obj_end(bldr); - fb_json_bldr_obj_end(bldr); - - json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); - fb_api_publish(api, "/messenger_sync_create_queue", "%s", - json); - g_free(json); - return; - } - - fb_json_bldr_add_int(bldr, "last_seq_id", priv->sid); - fb_json_bldr_add_str(bldr, "sync_token", priv->stoken); - - json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); - fb_api_publish(api, "/messenger_sync_get_diffs", "%s", json); - g_signal_emit_by_name(api, "connect"); - g_free(json); - - if (!g_queue_is_empty(priv->msgs)) { - msg = g_queue_peek_head(priv->msgs); - fb_api_message_send(api, msg); - } - - if (priv->retrying) { - priv->retrying = FALSE; - fb_util_debug_info("Reconnected the MQTT stream"); - } + FbApiMessage *msg; + FbApiPrivate *priv = api->priv; + gchar *json; + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_int(bldr, "delta_batch_size", 125); + fb_json_bldr_add_int(bldr, "max_deltas_able_to_process", 1250); + fb_json_bldr_add_int(bldr, "sync_api_version", 3); + fb_json_bldr_add_str(bldr, "encoding", "JSON"); + + if (priv->stoken == NULL) { + fb_json_bldr_add_int(bldr, "initial_titan_sequence_id", + priv->sid); + fb_json_bldr_add_str(bldr, "device_id", priv->did); + fb_json_bldr_add_int(bldr, "entity_fbid", priv->uid); + + fb_json_bldr_obj_begin(bldr, "queue_params"); + fb_json_bldr_add_str(bldr, "buzz_on_deltas_enabled", "false"); + + fb_json_bldr_obj_begin(bldr, "graphql_query_hashes"); + fb_json_bldr_add_str(bldr, "xma_query_id", + G_STRINGIFY(FB_API_QUERY_XMA)); + fb_json_bldr_obj_end(bldr); + + fb_json_bldr_obj_begin(bldr, "graphql_query_params"); + fb_json_bldr_obj_begin(bldr, G_STRINGIFY(FB_API_QUERY_XMA)); + fb_json_bldr_add_str(bldr, "xma_id", ""); + fb_json_bldr_obj_end(bldr); + fb_json_bldr_obj_end(bldr); + fb_json_bldr_obj_end(bldr); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/messenger_sync_create_queue", "%s", + json); + g_free(json); + return; + } + + fb_json_bldr_add_int(bldr, "last_seq_id", priv->sid); + fb_json_bldr_add_str(bldr, "sync_token", priv->stoken); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/messenger_sync_get_diffs", "%s", json); + g_signal_emit_by_name(api, "connect"); + g_free(json); + + if (!g_queue_is_empty(priv->msgs)) { + msg = g_queue_peek_head(priv->msgs); + fb_api_message_send(api, msg); + } + + if (priv->retrying) { + priv->retrying = FALSE; + fb_util_debug_info("Reconnected the MQTT stream"); + } } static void fb_api_cb_seqid(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - const gchar *str; - FbApi *api = data; - FbApiPrivate *priv = api->priv; - FbJsonValues *values; - GError *err = NULL; - JsonNode *root; - - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } - - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.viewer.message_threads.sync_sequence_id"); - fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, - "$.viewer.message_threads.unread_count"); - fb_json_values_update(values, &err); - - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return; - ); - - str = fb_json_values_next_str(values, "0"); - priv->sid = g_ascii_strtoll(str, NULL, 10); - priv->unread = fb_json_values_next_int(values, 0); - - if (priv->sid == 0) { - fb_api_error(api, FB_API_ERROR_GENERAL, - _("Failed to get sync_sequence_id")); - } else { - fb_api_connect_queue(api); - } - - g_object_unref(values); - json_node_free(root); + const gchar *str; + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.viewer.message_threads.sync_sequence_id"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, + "$.viewer.message_threads.unread_count"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + str = fb_json_values_next_str(values, "0"); + priv->sid = g_ascii_strtoll(str, NULL, 10); + priv->unread = fb_json_values_next_int(values, 0); + + if (priv->sid == 0) { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to get sync_sequence_id")); + } else { + fb_api_connect_queue(api); + } + + g_object_unref(values); + json_node_free(root); } static void fb_api_cb_mqtt_connect(FbMqtt *mqtt, gpointer data) { - FbApi *api = data; - FbApiPrivate *priv = api->priv; - gchar *json; - JsonBuilder *bldr; - - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_add_bool(bldr, "foreground", TRUE); - fb_json_bldr_add_int(bldr, "keepalive_timeout", FB_MQTT_KA); - - json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); - fb_api_publish(api, "/foreground_state", "%s", json); - g_free(json); - - fb_mqtt_subscribe(mqtt, - "/inbox", 0, - "/mercury", 0, - "/messaging_events", 0, - "/orca_presence", 0, - "/orca_typing_notifications", 0, - "/pp", 0, - "/t_ms", 0, - "/t_p", 0, - "/t_rtc", 0, - "/webrtc", 0, - "/webrtc_response", 0, - NULL - ); - - /* Notifications seem to lead to some sort of sending rate limit */ - fb_mqtt_unsubscribe(mqtt, "/orca_message_notifications", NULL); - - if (priv->sid == 0) { - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_add_str(bldr, "1", "0"); - fb_api_http_query(api, FB_API_QUERY_SEQ_ID, bldr, - fb_api_cb_seqid); - } else { - fb_api_connect_queue(api); - } + FbApi *api = data; + FbApiPrivate *priv = api->priv; + gchar *json; + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_bool(bldr, "foreground", TRUE); + fb_json_bldr_add_int(bldr, "keepalive_timeout", FB_MQTT_KA); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/foreground_state", "%s", json); + g_free(json); + + fb_mqtt_subscribe(mqtt, + "/inbox", 0, + "/mercury", 0, + "/messaging_events", 0, + "/orca_presence", 0, + "/orca_typing_notifications", 0, + "/pp", 0, + "/t_ms", 0, + "/t_p", 0, + "/t_rtc", 0, + "/webrtc", 0, + "/webrtc_response", 0, + NULL + ); + + /* Notifications seem to lead to some sort of sending rate limit */ + fb_mqtt_unsubscribe(mqtt, "/orca_message_notifications", NULL); + + if (priv->sid == 0) { + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "1", "0"); + fb_api_http_query(api, FB_API_QUERY_SEQ_ID, bldr, + fb_api_cb_seqid); + } else { + fb_api_connect_queue(api); + } } static void fb_api_cb_publish_mark(FbApi *api, GByteArray *pload) { - FbJsonValues *values; - GError *err = NULL; - JsonNode *root; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; - if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { - return; - } + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { + return; + } - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, "$.succeeded"); - fb_json_values_update(values, &err); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, "$.succeeded"); + fb_json_values_update(values, &err); - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return; - ); + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); - if (!fb_json_values_next_bool(values, TRUE)) { - fb_api_error(api, FB_API_ERROR_GENERAL, - _("Failed to mark thread as read")); - } + if (!fb_json_values_next_bool(values, TRUE)) { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to mark thread as read")); + } - g_object_unref(values); - json_node_free(root); + g_object_unref(values); + json_node_free(root); } static GSList * fb_api_event_parse(FbApi *api, FbApiEvent *event, GSList *events, JsonNode *root, GError **error) { - const gchar *str; - FbApiEvent *devent; - FbJsonValues *values; - GError *err = NULL; - guint i; - - static const struct { - FbApiEventType type; - const gchar *expr; - } evtypes[] = { - { - FB_API_EVENT_TYPE_THREAD_USER_ADDED, - "$.log_message_data.added_participants" - }, { - FB_API_EVENT_TYPE_THREAD_USER_REMOVED, - "$.log_message_data.removed_participants" - } - }; - - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.log_message_type"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.author"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.log_message_data.name"); - fb_json_values_update(values, &err); - - if (G_UNLIKELY(err != NULL)) { - g_propagate_error(error, err); - g_object_unref(values); - return events; - } - - str = fb_json_values_next_str(values, NULL); - - if (g_strcmp0(str, "log:thread-name") == 0) { - str = fb_json_values_next_str(values, ""); - str = strrchr(str, ':'); - - if (str != NULL) { - devent = fb_api_event_dup(event, FALSE); - devent->type = FB_API_EVENT_TYPE_THREAD_TOPIC; - devent->uid = FB_ID_FROM_STR(str + 1); - devent->text = fb_json_values_next_str_dup(values, NULL); - events = g_slist_prepend(events, devent); - } - } - - g_object_unref(values); - - for (i = 0; i < G_N_ELEMENTS(evtypes); i++) { - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$"); - fb_json_values_set_array(values, FALSE, evtypes[i].expr); - - while (fb_json_values_update(values, &err)) { - str = fb_json_values_next_str(values, ""); - str = strrchr(str, ':'); - - if (str != NULL) { - devent = fb_api_event_dup(event, FALSE); - devent->type = evtypes[i].type; - devent->uid = FB_ID_FROM_STR(str + 1); - events = g_slist_prepend(events, devent); - } - } - - g_object_unref(values); - - if (G_UNLIKELY(err != NULL)) { - g_propagate_error(error, err); - break; - } - } - - return events; + const gchar *str; + FbApiEvent *devent; + FbJsonValues *values; + GError *err = NULL; + guint i; + + static const struct { + FbApiEventType type; + const gchar *expr; + } evtypes[] = { + { + FB_API_EVENT_TYPE_THREAD_USER_ADDED, + "$.log_message_data.added_participants" + }, { + FB_API_EVENT_TYPE_THREAD_USER_REMOVED, + "$.log_message_data.removed_participants" + } + }; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.log_message_type"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.author"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.log_message_data.name"); + fb_json_values_update(values, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + g_object_unref(values); + return events; + } + + str = fb_json_values_next_str(values, NULL); + + if (g_strcmp0(str, "log:thread-name") == 0) { + str = fb_json_values_next_str(values, ""); + str = strrchr(str, ':'); + + if (str != NULL) { + devent = fb_api_event_dup(event, FALSE); + devent->type = FB_API_EVENT_TYPE_THREAD_TOPIC; + devent->uid = FB_ID_FROM_STR(str + 1); + devent->text = fb_json_values_next_str_dup(values, NULL); + events = g_slist_prepend(events, devent); + } + } + + g_object_unref(values); + + for (i = 0; i < G_N_ELEMENTS(evtypes); i++) { + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$"); + fb_json_values_set_array(values, FALSE, evtypes[i].expr); + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, ""); + str = strrchr(str, ':'); + + if (str != NULL) { + devent = fb_api_event_dup(event, FALSE); + devent->type = evtypes[i].type; + devent->uid = FB_ID_FROM_STR(str + 1); + events = g_slist_prepend(events, devent); + } + } + + g_object_unref(values); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + break; + } + } + + return events; } static void fb_api_cb_publish_mercury(FbApi *api, GByteArray *pload) { - const gchar *str; - FbApiEvent event; - FbJsonValues *values; - GError *err = NULL; - GSList *events = NULL; - JsonNode *root; - JsonNode *node; + const gchar *str; + FbApiEvent event; + FbJsonValues *values; + GError *err = NULL; + GSList *events = NULL; + JsonNode *root; + JsonNode *node; - if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { - return; - } + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { + return; + } - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.thread_fbid"); - fb_json_values_set_array(values, FALSE, "$.actions"); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.thread_fbid"); + fb_json_values_set_array(values, FALSE, "$.actions"); - while (fb_json_values_update(values, &err)) { - fb_api_event_reset(&event, FALSE); - str = fb_json_values_next_str(values, "0"); - event.tid = FB_ID_FROM_STR(str); + while (fb_json_values_update(values, &err)) { + fb_api_event_reset(&event, FALSE); + str = fb_json_values_next_str(values, "0"); + event.tid = FB_ID_FROM_STR(str); - node = fb_json_values_get_root(values); - events = fb_api_event_parse(api, &event, events, node, &err); - } + node = fb_json_values_get_root(values); + events = fb_api_event_parse(api, &event, events, node, &err); + } - if (G_LIKELY(err == NULL)) { - events = g_slist_reverse(events); - g_signal_emit_by_name(api, "events", events); - } else { - fb_api_error_emit(api, err); - } + if (G_LIKELY(err == NULL)) { + events = g_slist_reverse(events); + g_signal_emit_by_name(api, "events", events); + } else { + fb_api_error_emit(api, err); + } - g_slist_free_full(events, (GDestroyNotify) fb_api_event_free); - g_object_unref(values); - json_node_free(root); + g_slist_free_full(events, (GDestroyNotify) fb_api_event_free); + g_object_unref(values); + json_node_free(root); } static void fb_api_cb_publish_typing(FbApi *api, GByteArray *pload) { - const gchar *str; - FbApiPrivate *priv = api->priv; - FbApiTyping typg; - FbJsonValues *values; - GError *err = NULL; - JsonNode *root; + const gchar *str; + FbApiPrivate *priv = api->priv; + FbApiTyping typg; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; - if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { - return; - } + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { + return; + } - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.type"); - fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.sender_fbid"); - fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.state"); - fb_json_values_update(values, &err); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.type"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.sender_fbid"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.state"); + fb_json_values_update(values, &err); - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return; - ); + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); - str = fb_json_values_next_str(values, NULL); + str = fb_json_values_next_str(values, NULL); - if (g_ascii_strcasecmp(str, "typ") == 0) { - typg.uid = fb_json_values_next_int(values, 0); + if (g_ascii_strcasecmp(str, "typ") == 0) { + typg.uid = fb_json_values_next_int(values, 0); - if (typg.uid != priv->uid) { - typg.state = fb_json_values_next_int(values, 0); - g_signal_emit_by_name(api, "typing", &typg); - } - } + if (typg.uid != priv->uid) { + typg.state = fb_json_values_next_int(values, 0); + g_signal_emit_by_name(api, "typing", &typg); + } + } - g_object_unref(values); - json_node_free(root); + g_object_unref(values); + json_node_free(root); } static void fb_api_cb_publish_ms_r(FbApi *api, GByteArray *pload) { - FbApiMessage *msg; - FbApiPrivate *priv = api->priv; - FbJsonValues *values; - GError *err = NULL; - JsonNode *root; + FbApiMessage *msg; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; - if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { - return; - } + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { + return; + } - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.succeeded"); - fb_json_values_update(values, &err); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.succeeded"); + fb_json_values_update(values, &err); - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return; - ); + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); - if (fb_json_values_next_bool(values, TRUE)) { - /* Pop and free the successful message */ - msg = g_queue_pop_head(priv->msgs); - fb_api_message_free(msg); + if (fb_json_values_next_bool(values, TRUE)) { + /* Pop and free the successful message */ + msg = g_queue_pop_head(priv->msgs); + fb_api_message_free(msg); - if (!g_queue_is_empty(priv->msgs)) { - msg = g_queue_peek_head(priv->msgs); - fb_api_message_send(api, msg); - } - } else { - fb_api_error(api, FB_API_ERROR_GENERAL, - "Failed to send message"); - } + if (!g_queue_is_empty(priv->msgs)) { + msg = g_queue_peek_head(priv->msgs); + fb_api_message_send(api, msg); + } + } else { + fb_api_error(api, FB_API_ERROR_GENERAL, + "Failed to send message"); + } - g_object_unref(values); - json_node_free(root); + g_object_unref(values); + json_node_free(root); } static gchar * fb_api_xma_parse(FbApi *api, const gchar *body, JsonNode *root, GError **error) { - const gchar *str; - const gchar *url; - FbHttpParams *params; - FbJsonValues *values; - gchar *text; - GError *err = NULL; - - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.story_attachment.target.__type__.name"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.story_attachment.url"); - fb_json_values_update(values, &err); - - if (G_UNLIKELY(err != NULL)) { - g_propagate_error(error, err); - g_object_unref(values); - return NULL; - } - - str = fb_json_values_next_str(values, NULL); - url = fb_json_values_next_str(values, NULL); - - if ((str == NULL) || (url == NULL)) { - text = g_strdup(_("")); - g_object_unref(values); - return text; - } - - if (purple_strequal(str, "ExternalUrl")) { - params = fb_http_params_new_parse(url, TRUE); - if (g_str_has_prefix(url, FB_API_FBRPC_PREFIX)) { - text = fb_http_params_dup_str(params, "target_url", NULL); - } else { - text = fb_http_params_dup_str(params, "u", NULL); - } - fb_http_params_free(params); - } else { - text = g_strdup(url); - } - - if (fb_http_urlcmp(body, text, FALSE)) { - g_free(text); - g_object_unref(values); - return NULL; - } - - g_object_unref(values); - return text; + const gchar *str; + const gchar *url; + FbHttpParams *params; + FbJsonValues *values; + gchar *text; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.story_attachment.target.__type__.name"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.story_attachment.url"); + fb_json_values_update(values, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + g_object_unref(values); + return NULL; + } + + str = fb_json_values_next_str(values, NULL); + url = fb_json_values_next_str(values, NULL); + + if ((str == NULL) || (url == NULL)) { + text = g_strdup(_("")); + g_object_unref(values); + return text; + } + + if (purple_strequal(str, "ExternalUrl")) { + params = fb_http_params_new_parse(url, TRUE); + if (g_str_has_prefix(url, FB_API_FBRPC_PREFIX)) { + text = fb_http_params_dup_str(params, "target_url", NULL); + } else { + text = fb_http_params_dup_str(params, "u", NULL); + } + fb_http_params_free(params); + } else { + text = g_strdup(url); + } + + if (fb_http_urlcmp(body, text, FALSE)) { + g_free(text); + g_object_unref(values); + return NULL; + } + + g_object_unref(values); + return text; } static GSList * @@ -1440,58 +1440,58 @@ fb_api_message_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg, GSList *msgs, const gchar *body, JsonNode *root, GError **error) { - const gchar *str; - FbApiMessage *dmsg; - FbId id; - FbJsonValues *values; - gchar *xma; - GError *err = NULL; - JsonNode *node; - JsonNode *xode; + const gchar *str; + FbApiMessage *dmsg; + FbId id; + FbJsonValues *values; + gchar *xma; + GError *err = NULL; + JsonNode *node; + JsonNode *xode; - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.xmaGraphQL"); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.fbid"); - fb_json_values_set_array(values, FALSE, "$.attachments"); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.xmaGraphQL"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.fbid"); + fb_json_values_set_array(values, FALSE, "$.attachments"); - while (fb_json_values_update(values, &err)) { - str = fb_json_values_next_str(values, NULL); + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, NULL); - if (str == NULL) { - id = fb_json_values_next_int(values, 0); - dmsg = fb_api_message_dup(msg, FALSE); - fb_api_attach(api, id, mid, dmsg); - continue; - } + if (str == NULL) { + id = fb_json_values_next_int(values, 0); + dmsg = fb_api_message_dup(msg, FALSE); + fb_api_attach(api, id, mid, dmsg); + continue; + } - node = fb_json_node_new(str, -1, &err); + node = fb_json_node_new(str, -1, &err); - if (G_UNLIKELY(err != NULL)) { - break; - } + if (G_UNLIKELY(err != NULL)) { + break; + } - xode = fb_json_node_get_nth(node, 0); - xma = fb_api_xma_parse(api, body, xode, &err); + xode = fb_json_node_get_nth(node, 0); + xma = fb_api_xma_parse(api, body, xode, &err); - if (xma != NULL) { - dmsg = fb_api_message_dup(msg, FALSE); - dmsg->text = xma; - msgs = g_slist_prepend(msgs, dmsg); - } + if (xma != NULL) { + dmsg = fb_api_message_dup(msg, FALSE); + dmsg->text = xma; + msgs = g_slist_prepend(msgs, dmsg); + } - json_node_free(node); + json_node_free(node); - if (G_UNLIKELY(err != NULL)) { - break; - } - } + if (G_UNLIKELY(err != NULL)) { + break; + } + } - if (G_UNLIKELY(err != NULL)) { - g_propagate_error(error, err); - } + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + } - g_object_unref(values); - return msgs; + g_object_unref(values); + return msgs; } @@ -1504,2036 +1504,2036 @@ fb_api_cb_publish_ms_event(FbApi *api, JsonNode *root, GSList *events, FbApiEven static void fb_api_cb_publish_mst(FbThrift *thft, GError **error) { - if (fb_thrift_read_isstop(thft)) { - FB_API_TCHK(fb_thrift_read_stop(thft)); - } else { - FbThriftType type; - gint16 id; + if (fb_thrift_read_isstop(thft)) { + FB_API_TCHK(fb_thrift_read_stop(thft)); + } else { + FbThriftType type; + gint16 id; - FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); - FB_API_TCHK(type == FB_THRIFT_TYPE_STRING); - // FB_API_TCHK(id == 2); - FB_API_TCHK(fb_thrift_read_str(thft, NULL)); - FB_API_TCHK(fb_thrift_read_stop(thft)); - } + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); + FB_API_TCHK(type == FB_THRIFT_TYPE_STRING); + // FB_API_TCHK(id == 2); + FB_API_TCHK(fb_thrift_read_str(thft, NULL)); + FB_API_TCHK(fb_thrift_read_stop(thft)); + } } static void fb_api_cb_publish_ms(FbApi *api, GByteArray *pload) { - const gchar *data; - FbApiPrivate *priv = api->priv; - FbJsonValues *values; - FbThrift *thft; - gchar *stoken; - GError *err = NULL; - GList *elms, *l; - GSList *msgs = NULL; - GSList *events = NULL; - guint size; - JsonNode *root; - JsonNode *node; - JsonArray *arr; - - static const struct { - const gchar *member; - FbApiEventType type; - gboolean is_message; - } event_types[] = { - {"deltaNewMessage", 0, 1}, - {"deltaThreadName", FB_API_EVENT_TYPE_THREAD_TOPIC, 0}, - {"deltaParticipantsAddedToGroupThread", FB_API_EVENT_TYPE_THREAD_USER_ADDED, 0}, - {"deltaParticipantLeftGroupThread", FB_API_EVENT_TYPE_THREAD_USER_REMOVED, 0}, - }; - - /* Read identifier string (for Facebook employees) */ - thft = fb_thrift_new(pload, 0); - fb_api_cb_publish_mst(thft, &err); - size = fb_thrift_get_pos(thft); - g_object_unref(thft); - - FB_API_ERROR_EMIT(api, err, - return; - ); - - g_return_if_fail(size < pload->len); - data = (gchar *) pload->data + size; - size = pload->len - size; - - if (!fb_api_json_chk(api, data, size, &root)) { - return; - } - - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.lastIssuedSeqId"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.syncToken"); - fb_json_values_update(values, &err); - - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return; - ); - - priv->sid = fb_json_values_next_int(values, 0); - stoken = fb_json_values_next_str_dup(values, NULL); - g_object_unref(values); - - if (G_UNLIKELY(stoken != NULL)) { - g_free(priv->stoken); - priv->stoken = stoken; - g_signal_emit_by_name(api, "connect"); - json_node_free(root); - return; - } - - arr = fb_json_node_get_arr(root, "$.deltas", NULL); - elms = json_array_get_elements(arr); - - for (l = elms; l != NULL; l = l->next) { - guint i = 0; - JsonObject *o = json_node_get_object(l->data); - - for (i = 0; i < G_N_ELEMENTS(event_types); i++) { - if ((node = json_object_get_member(o, event_types[i].member))) { - if (event_types[i].is_message) { - msgs = fb_api_cb_publish_ms_new_message( - api, node, msgs, &err - ); - } else { - events = fb_api_cb_publish_ms_event( - api, node, events, event_types[i].type, &err - ); - } - } - } - - if (G_UNLIKELY(err != NULL)) { - break; - } - } - - g_list_free(elms); - json_array_unref(arr); - - if (G_LIKELY(err == NULL)) { - if (msgs) { - msgs = g_slist_reverse(msgs); - g_signal_emit_by_name(api, "messages", msgs); - } - - if (events) { - events = g_slist_reverse(events); - g_signal_emit_by_name(api, "events", events); - } - } else { - fb_api_error_emit(api, err); - } - - g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); - g_slist_free_full(events, (GDestroyNotify) fb_api_event_free); - json_node_free(root); + const gchar *data; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + FbThrift *thft; + gchar *stoken; + GError *err = NULL; + GList *elms, *l; + GSList *msgs = NULL; + GSList *events = NULL; + guint size; + JsonNode *root; + JsonNode *node; + JsonArray *arr; + + static const struct { + const gchar *member; + FbApiEventType type; + gboolean is_message; + } event_types[] = { + {"deltaNewMessage", 0, 1}, + {"deltaThreadName", FB_API_EVENT_TYPE_THREAD_TOPIC, 0}, + {"deltaParticipantsAddedToGroupThread", FB_API_EVENT_TYPE_THREAD_USER_ADDED, 0}, + {"deltaParticipantLeftGroupThread", FB_API_EVENT_TYPE_THREAD_USER_REMOVED, 0}, + }; + + /* Read identifier string (for Facebook employees) */ + thft = fb_thrift_new(pload, 0); + fb_api_cb_publish_mst(thft, &err); + size = fb_thrift_get_pos(thft); + g_object_unref(thft); + + FB_API_ERROR_EMIT(api, err, + return; + ); + + g_return_if_fail(size < pload->len); + data = (gchar *) pload->data + size; + size = pload->len - size; + + if (!fb_api_json_chk(api, data, size, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.lastIssuedSeqId"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.syncToken"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + priv->sid = fb_json_values_next_int(values, 0); + stoken = fb_json_values_next_str_dup(values, NULL); + g_object_unref(values); + + if (G_UNLIKELY(stoken != NULL)) { + g_free(priv->stoken); + priv->stoken = stoken; + g_signal_emit_by_name(api, "connect"); + json_node_free(root); + return; + } + + arr = fb_json_node_get_arr(root, "$.deltas", NULL); + elms = json_array_get_elements(arr); + + for (l = elms; l != NULL; l = l->next) { + guint i = 0; + JsonObject *o = json_node_get_object(l->data); + + for (i = 0; i < G_N_ELEMENTS(event_types); i++) { + if ((node = json_object_get_member(o, event_types[i].member))) { + if (event_types[i].is_message) { + msgs = fb_api_cb_publish_ms_new_message( + api, node, msgs, &err + ); + } else { + events = fb_api_cb_publish_ms_event( + api, node, events, event_types[i].type, &err + ); + } + } + } + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + g_list_free(elms); + json_array_unref(arr); + + if (G_LIKELY(err == NULL)) { + if (msgs) { + msgs = g_slist_reverse(msgs); + g_signal_emit_by_name(api, "messages", msgs); + } + + if (events) { + events = g_slist_reverse(events); + g_signal_emit_by_name(api, "events", events); + } + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_slist_free_full(events, (GDestroyNotify) fb_api_event_free); + json_node_free(root); } static GSList * fb_api_cb_publish_ms_new_message(FbApi *api, JsonNode *root, GSList *msgs, GError **error) { - const gchar *body; - const gchar *str; - GError *err = NULL; - FbApiPrivate *priv = api->priv; - FbApiMessage *dmsg; - FbApiMessage msg; - FbId id; - FbId oid; - FbJsonValues *values; - JsonNode *node; - - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.messageMetadata.offlineThreadingId"); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.messageMetadata.actorFbId"); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.messageMetadata" - ".threadKey.otherUserFbId"); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.messageMetadata" - ".threadKey.threadFbId"); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.messageMetadata.timestamp"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.body"); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.stickerId"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.messageMetadata.messageId"); - - if (fb_json_values_update(values, &err)) { - id = fb_json_values_next_int(values, 0); - - /* Ignore everything but new messages */ - if (id == 0) { - goto beach; - } - - /* Ignore sequential duplicates */ - if (id == priv->lastmid) { - fb_util_debug_info("Ignoring duplicate %" FB_ID_FORMAT, id); - goto beach; - } - - priv->lastmid = id; - fb_api_message_reset(&msg, FALSE); - msg.uid = fb_json_values_next_int(values, 0); - oid = fb_json_values_next_int(values, 0); - msg.tid = fb_json_values_next_int(values, 0); - msg.tstamp = fb_json_values_next_int(values, 0); - - if (msg.uid == priv->uid) { - msg.flags |= FB_API_MESSAGE_FLAG_SELF; - - if (msg.tid == 0) { - msg.uid = oid; - } - } - - body = fb_json_values_next_str(values, NULL); - - if (body != NULL) { - dmsg = fb_api_message_dup(&msg, FALSE); - dmsg->text = g_strdup(body); - msgs = g_slist_prepend(msgs, dmsg); - } - - id = fb_json_values_next_int(values, 0); - - if (id != 0) { - dmsg = fb_api_message_dup(&msg, FALSE); - fb_api_sticker(api, id, dmsg); - } - - str = fb_json_values_next_str(values, NULL); - - if (str == NULL) { - goto beach; - } - - node = fb_json_values_get_root(values); - msgs = fb_api_message_parse_attach(api, str, &msg, msgs, body, - node, &err); - - if (G_UNLIKELY(err != NULL)) { - g_propagate_error(error, err); - goto beach; - } - } + const gchar *body; + const gchar *str; + GError *err = NULL; + FbApiPrivate *priv = api->priv; + FbApiMessage *dmsg; + FbApiMessage msg; + FbId id; + FbId oid; + FbJsonValues *values; + JsonNode *node; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata.offlineThreadingId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata.actorFbId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata" + ".threadKey.otherUserFbId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata" + ".threadKey.threadFbId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata.timestamp"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.body"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.stickerId"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.messageMetadata.messageId"); + + if (fb_json_values_update(values, &err)) { + id = fb_json_values_next_int(values, 0); + + /* Ignore everything but new messages */ + if (id == 0) { + goto beach; + } + + /* Ignore sequential duplicates */ + if (id == priv->lastmid) { + fb_util_debug_info("Ignoring duplicate %" FB_ID_FORMAT, id); + goto beach; + } + + priv->lastmid = id; + fb_api_message_reset(&msg, FALSE); + msg.uid = fb_json_values_next_int(values, 0); + oid = fb_json_values_next_int(values, 0); + msg.tid = fb_json_values_next_int(values, 0); + msg.tstamp = fb_json_values_next_int(values, 0); + + if (msg.uid == priv->uid) { + msg.flags |= FB_API_MESSAGE_FLAG_SELF; + + if (msg.tid == 0) { + msg.uid = oid; + } + } + + body = fb_json_values_next_str(values, NULL); + + if (body != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + dmsg->text = g_strdup(body); + msgs = g_slist_prepend(msgs, dmsg); + } + + id = fb_json_values_next_int(values, 0); + + if (id != 0) { + dmsg = fb_api_message_dup(&msg, FALSE); + fb_api_sticker(api, id, dmsg); + } + + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + goto beach; + } + + node = fb_json_values_get_root(values); + msgs = fb_api_message_parse_attach(api, str, &msg, msgs, body, + node, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + goto beach; + } + } beach: - g_object_unref(values); - return msgs; + g_object_unref(values); + return msgs; } static GSList * fb_api_cb_publish_ms_event(FbApi *api, JsonNode *root, GSList *events, FbApiEventType type, GError **error) { - FbApiEvent *event; - FbJsonValues *values = NULL; - FbJsonValues *values_inner = NULL; - GError *err = NULL; + FbApiEvent *event; + FbJsonValues *values = NULL; + FbJsonValues *values_inner = NULL; + GError *err = NULL; - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.messageMetadata.threadKey.threadFbId"); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.messageMetadata.actorFbId"); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata.threadKey.threadFbId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.messageMetadata.actorFbId"); - switch (type) { - case FB_API_EVENT_TYPE_THREAD_TOPIC: - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.name"); - break; + switch (type) { + case FB_API_EVENT_TYPE_THREAD_TOPIC: + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.name"); + break; - case FB_API_EVENT_TYPE_THREAD_USER_ADDED: - values_inner = fb_json_values_new(root); + case FB_API_EVENT_TYPE_THREAD_USER_ADDED: + values_inner = fb_json_values_new(root); - fb_json_values_add(values_inner, FB_JSON_TYPE_INT, FALSE, - "$.userFbId"); + fb_json_values_add(values_inner, FB_JSON_TYPE_INT, FALSE, + "$.userFbId"); - /* use the text field for the full name */ - fb_json_values_add(values_inner, FB_JSON_TYPE_STR, FALSE, - "$.fullName"); + /* use the text field for the full name */ + fb_json_values_add(values_inner, FB_JSON_TYPE_STR, FALSE, + "$.fullName"); - fb_json_values_set_array(values_inner, FALSE, - "$.addedParticipants"); - break; + fb_json_values_set_array(values_inner, FALSE, + "$.addedParticipants"); + break; - case FB_API_EVENT_TYPE_THREAD_USER_REMOVED: - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.leftParticipantFbId"); + case FB_API_EVENT_TYPE_THREAD_USER_REMOVED: + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.leftParticipantFbId"); - /* use the text field for the kick message */ - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.messageMetadata.adminText"); - break; - } + /* use the text field for the kick message */ + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.messageMetadata.adminText"); + break; + } - fb_json_values_update(values, &err); + fb_json_values_update(values, &err); - event = fb_api_event_dup(NULL, FALSE); - event->type = type; - event->tid = fb_json_values_next_int(values, 0); - event->uid = fb_json_values_next_int(values, 0); + event = fb_api_event_dup(NULL, FALSE); + event->type = type; + event->tid = fb_json_values_next_int(values, 0); + event->uid = fb_json_values_next_int(values, 0); - if (type == FB_API_EVENT_TYPE_THREAD_TOPIC) { - event->text = fb_json_values_next_str_dup(values, NULL); - } else if (type == FB_API_EVENT_TYPE_THREAD_USER_REMOVED) { - /* overwrite actor with subject */ - event->uid = fb_json_values_next_int(values, 0); - event->text = fb_json_values_next_str_dup(values, NULL); - } else if (type == FB_API_EVENT_TYPE_THREAD_USER_ADDED) { + if (type == FB_API_EVENT_TYPE_THREAD_TOPIC) { + event->text = fb_json_values_next_str_dup(values, NULL); + } else if (type == FB_API_EVENT_TYPE_THREAD_USER_REMOVED) { + /* overwrite actor with subject */ + event->uid = fb_json_values_next_int(values, 0); + event->text = fb_json_values_next_str_dup(values, NULL); + } else if (type == FB_API_EVENT_TYPE_THREAD_USER_ADDED) { - while (fb_json_values_update(values_inner, &err)) { - FbApiEvent *devent = fb_api_event_dup(event, FALSE); + while (fb_json_values_update(values_inner, &err)) { + FbApiEvent *devent = fb_api_event_dup(event, FALSE); - devent->uid = fb_json_values_next_int(values_inner, 0); - devent->text = fb_json_values_next_str_dup(values_inner, NULL); + devent->uid = fb_json_values_next_int(values_inner, 0); + devent->text = fb_json_values_next_str_dup(values_inner, NULL); - events = g_slist_prepend(events, devent); - } - fb_api_event_free(event); - event = NULL; - g_object_unref(values_inner); - } + events = g_slist_prepend(events, devent); + } + fb_api_event_free(event); + event = NULL; + g_object_unref(values_inner); + } - g_object_unref(values); + g_object_unref(values); - if (G_UNLIKELY(err != NULL)) { - g_propagate_error(error, err); - } else if (event) { - events = g_slist_prepend(events, event); - } + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + } else if (event) { + events = g_slist_prepend(events, event); + } - return events; + return events; } static void fb_api_cb_publish_pt(FbThrift *thft, GSList **press, GError **error) { - FbApiPresence *pres; - FbThriftType type; - gint16 id; - gint32 i32; - gint64 i64; - guint i; - guint size = 0; - - /* Read identifier string (for Facebook employees) */ - FB_API_TCHK(fb_thrift_read_str(thft, NULL)); - - /* Read the full list boolean field */ - FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); - FB_API_TCHK(type == FB_THRIFT_TYPE_BOOL); - FB_API_TCHK(id == 1); - FB_API_TCHK(fb_thrift_read_bool(thft, NULL)); - - /* Read the list field */ - FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id)); - FB_API_TCHK(type == FB_THRIFT_TYPE_LIST); - FB_API_TCHK(id == 2); - - /* Read the list */ - FB_API_TCHK(fb_thrift_read_list(thft, &type, &size)); - FB_API_TCHK(type == FB_THRIFT_TYPE_STRUCT); - - for (i = 0; i < size; i++) { - /* Read the user identifier field */ - FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); - FB_API_TCHK(type == FB_THRIFT_TYPE_I64); - FB_API_TCHK(id == 1); - FB_API_TCHK(fb_thrift_read_i64(thft, &i64)); - - /* Read the active field */ - FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id)); - FB_API_TCHK(type == FB_THRIFT_TYPE_I32); - FB_API_TCHK(id == 2); - FB_API_TCHK(fb_thrift_read_i32(thft, &i32)); - - pres = fb_api_presence_dup(NULL); - pres->uid = i64; - pres->active = i32 != 0; - *press = g_slist_prepend(*press, pres); - - fb_util_debug_info("Presence: %" FB_ID_FORMAT " (%d) id: %d", - i64, i32 != 0, id); - - while (id <= 6) { - if (fb_thrift_read_isstop(thft)) { - break; - } - - FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id)); - - switch (id) { - case 3: - /* Read the last active timestamp field */ - FB_API_TCHK(type == FB_THRIFT_TYPE_I64); - FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); - break; - - case 4: - /* Read the active client bits field */ - FB_API_TCHK(type == FB_THRIFT_TYPE_I16); - FB_API_TCHK(fb_thrift_read_i16(thft, NULL)); - break; - - case 5: - /* Read the VoIP compatibility bits field */ - FB_API_TCHK(type == FB_THRIFT_TYPE_I64); - FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); - break; - - case 6: - /* Unknown new field */ - FB_API_TCHK(type == FB_THRIFT_TYPE_I64); - FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); - break; - - default: - /* Try to read unknown fields as varint */ - FB_API_TCHK(type == FB_THRIFT_TYPE_I16 || - type == FB_THRIFT_TYPE_I32 || - type == FB_THRIFT_TYPE_I64); - FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); - break; - } - } - - /* Read the field stop */ - FB_API_TCHK(fb_thrift_read_stop(thft)); - } - - /* Read the field stop */ - if (fb_thrift_read_isstop(thft)) { - FB_API_TCHK(fb_thrift_read_stop(thft)); - } + FbApiPresence *pres; + FbThriftType type; + gint16 id; + gint32 i32; + gint64 i64; + guint i; + guint size = 0; + + /* Read identifier string (for Facebook employees) */ + FB_API_TCHK(fb_thrift_read_str(thft, NULL)); + + /* Read the full list boolean field */ + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); + FB_API_TCHK(type == FB_THRIFT_TYPE_BOOL); + FB_API_TCHK(id == 1); + FB_API_TCHK(fb_thrift_read_bool(thft, NULL)); + + /* Read the list field */ + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id)); + FB_API_TCHK(type == FB_THRIFT_TYPE_LIST); + FB_API_TCHK(id == 2); + + /* Read the list */ + FB_API_TCHK(fb_thrift_read_list(thft, &type, &size)); + FB_API_TCHK(type == FB_THRIFT_TYPE_STRUCT); + + for (i = 0; i < size; i++) { + /* Read the user identifier field */ + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); + FB_API_TCHK(type == FB_THRIFT_TYPE_I64); + FB_API_TCHK(id == 1); + FB_API_TCHK(fb_thrift_read_i64(thft, &i64)); + + /* Read the active field */ + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id)); + FB_API_TCHK(type == FB_THRIFT_TYPE_I32); + FB_API_TCHK(id == 2); + FB_API_TCHK(fb_thrift_read_i32(thft, &i32)); + + pres = fb_api_presence_dup(NULL); + pres->uid = i64; + pres->active = i32 != 0; + *press = g_slist_prepend(*press, pres); + + fb_util_debug_info("Presence: %" FB_ID_FORMAT " (%d) id: %d", + i64, i32 != 0, id); + + while (id <= 6) { + if (fb_thrift_read_isstop(thft)) { + break; + } + + FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id)); + + switch (id) { + case 3: + /* Read the last active timestamp field */ + FB_API_TCHK(type == FB_THRIFT_TYPE_I64); + FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); + break; + + case 4: + /* Read the active client bits field */ + FB_API_TCHK(type == FB_THRIFT_TYPE_I16); + FB_API_TCHK(fb_thrift_read_i16(thft, NULL)); + break; + + case 5: + /* Read the VoIP compatibility bits field */ + FB_API_TCHK(type == FB_THRIFT_TYPE_I64); + FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); + break; + + case 6: + /* Unknown new field */ + FB_API_TCHK(type == FB_THRIFT_TYPE_I64); + FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); + break; + + default: + /* Try to read unknown fields as varint */ + FB_API_TCHK(type == FB_THRIFT_TYPE_I16 || + type == FB_THRIFT_TYPE_I32 || + type == FB_THRIFT_TYPE_I64); + FB_API_TCHK(fb_thrift_read_i64(thft, NULL)); + break; + } + } + + /* Read the field stop */ + FB_API_TCHK(fb_thrift_read_stop(thft)); + } + + /* Read the field stop */ + if (fb_thrift_read_isstop(thft)) { + FB_API_TCHK(fb_thrift_read_stop(thft)); + } } static void fb_api_cb_publish_p(FbApi *api, GByteArray *pload) { - FbThrift *thft; - GError *err = NULL; - GSList *press = NULL; + FbThrift *thft; + GError *err = NULL; + GSList *press = NULL; - thft = fb_thrift_new(pload, 0); - fb_api_cb_publish_pt(thft, &press, &err); - g_object_unref(thft); + thft = fb_thrift_new(pload, 0); + fb_api_cb_publish_pt(thft, &press, &err); + g_object_unref(thft); - if (G_LIKELY(err == NULL)) { - g_signal_emit_by_name(api, "presences", press); - } else { - fb_api_error_emit(api, err); - } + if (G_LIKELY(err == NULL)) { + g_signal_emit_by_name(api, "presences", press); + } else { + fb_api_error_emit(api, err); + } - g_slist_free_full(press, (GDestroyNotify) fb_api_presence_free); + g_slist_free_full(press, (GDestroyNotify) fb_api_presence_free); } static void fb_api_cb_mqtt_publish(FbMqtt *mqtt, const gchar *topic, GByteArray *pload, gpointer data) { - FbApi *api = data; - gboolean comp; - GByteArray *bytes; - GError *err = NULL; - guint i; - - static const struct { - const gchar *topic; - void (*func) (FbApi *api, GByteArray *pload); - } parsers[] = { - {"/mark_thread_response", fb_api_cb_publish_mark}, - {"/mercury", fb_api_cb_publish_mercury}, - {"/orca_typing_notifications", fb_api_cb_publish_typing}, - {"/send_message_response", fb_api_cb_publish_ms_r}, - {"/t_ms", fb_api_cb_publish_ms}, - {"/t_p", fb_api_cb_publish_p} - }; - - comp = fb_util_zlib_test(pload); - - if (G_LIKELY(comp)) { - bytes = fb_util_zlib_inflate(pload, &err); - FB_API_ERROR_EMIT(api, err, return); - } else { - bytes = (GByteArray *) pload; - } - - fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, - "Reading message (topic: %s)", - topic); - - for (i = 0; i < G_N_ELEMENTS(parsers); i++) { - if (g_ascii_strcasecmp(topic, parsers[i].topic) == 0) { - parsers[i].func(api, bytes); - break; - } - } - - if (G_LIKELY(comp)) { - g_byte_array_free(bytes, TRUE); - } + FbApi *api = data; + gboolean comp; + GByteArray *bytes; + GError *err = NULL; + guint i; + + static const struct { + const gchar *topic; + void (*func) (FbApi *api, GByteArray *pload); + } parsers[] = { + {"/mark_thread_response", fb_api_cb_publish_mark}, + {"/mercury", fb_api_cb_publish_mercury}, + {"/orca_typing_notifications", fb_api_cb_publish_typing}, + {"/send_message_response", fb_api_cb_publish_ms_r}, + {"/t_ms", fb_api_cb_publish_ms}, + {"/t_p", fb_api_cb_publish_p} + }; + + comp = fb_util_zlib_test(pload); + + if (G_LIKELY(comp)) { + bytes = fb_util_zlib_inflate(pload, &err); + FB_API_ERROR_EMIT(api, err, return); + } else { + bytes = (GByteArray *) pload; + } + + fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, + "Reading message (topic: %s)", + topic); + + for (i = 0; i < G_N_ELEMENTS(parsers); i++) { + if (g_ascii_strcasecmp(topic, parsers[i].topic) == 0) { + parsers[i].func(api, bytes); + break; + } + } + + if (G_LIKELY(comp)) { + g_byte_array_free(bytes, TRUE); + } } FbApi * fb_api_new(PurpleConnection *gc) { - FbApi *api; - FbApiPrivate *priv; + FbApi *api; + FbApiPrivate *priv; - api = g_object_new(FB_TYPE_API, NULL); - priv = api->priv; + api = g_object_new(FB_TYPE_API, NULL); + priv = api->priv; - priv->gc = gc; - priv->mqtt = fb_mqtt_new(gc); + priv->gc = gc; + priv->mqtt = fb_mqtt_new(gc); - g_signal_connect(priv->mqtt, - "connect", - G_CALLBACK(fb_api_cb_mqtt_connect), - api); - g_signal_connect(priv->mqtt, - "error", - G_CALLBACK(fb_api_cb_mqtt_error), - api); - g_signal_connect(priv->mqtt, - "open", - G_CALLBACK(fb_api_cb_mqtt_open), - api); - g_signal_connect(priv->mqtt, - "publish", - G_CALLBACK(fb_api_cb_mqtt_publish), - api); + g_signal_connect(priv->mqtt, + "connect", + G_CALLBACK(fb_api_cb_mqtt_connect), + api); + g_signal_connect(priv->mqtt, + "error", + G_CALLBACK(fb_api_cb_mqtt_error), + api); + g_signal_connect(priv->mqtt, + "open", + G_CALLBACK(fb_api_cb_mqtt_open), + api); + g_signal_connect(priv->mqtt, + "publish", + G_CALLBACK(fb_api_cb_mqtt_publish), + api); - return api; + return api; } void fb_api_rehash(FbApi *api) { - FbApiPrivate *priv; + FbApiPrivate *priv; - g_return_if_fail(FB_IS_API(api)); - priv = api->priv; + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - if (priv->cid == NULL) { - priv->cid = fb_util_rand_alnum(32); - } + if (priv->cid == NULL) { + priv->cid = fb_util_rand_alnum(32); + } - if (priv->did == NULL) { - priv->did = purple_uuid_random(); - } + if (priv->did == NULL) { + priv->did = purple_uuid_random(); + } - if (priv->mid == 0) { - priv->mid = g_random_int(); - } + if (priv->mid == 0) { + priv->mid = g_random_int(); + } - if (strlen(priv->cid) > 20) { - priv->cid = g_realloc_n(priv->cid , 21, sizeof *priv->cid); - priv->cid[20] = 0; - } + if (strlen(priv->cid) > 20) { + priv->cid = g_realloc_n(priv->cid , 21, sizeof *priv->cid); + priv->cid[20] = 0; + } } gboolean fb_api_is_invisible(FbApi *api) { - FbApiPrivate *priv; + FbApiPrivate *priv; - g_return_val_if_fail(FB_IS_API(api), FALSE); - priv = api->priv; + g_return_val_if_fail(FB_IS_API(api), FALSE); + priv = api->priv; - return priv->invisible; + return priv->invisible; } void fb_api_error(FbApi *api, FbApiError error, const gchar *format, ...) { - GError *err; - va_list ap; + GError *err; + va_list ap; - g_return_if_fail(FB_IS_API(api)); + g_return_if_fail(FB_IS_API(api)); - va_start(ap, format); - err = g_error_new_valist(FB_API_ERROR, error, format, ap); - va_end(ap); + va_start(ap, format); + err = g_error_new_valist(FB_API_ERROR, error, format, ap); + va_end(ap); - fb_api_error_emit(api, err); + fb_api_error_emit(api, err); } void fb_api_error_emit(FbApi *api, GError *error) { - g_return_if_fail(FB_IS_API(api)); - g_return_if_fail(error != NULL); + g_return_if_fail(FB_IS_API(api)); + g_return_if_fail(error != NULL); - g_signal_emit_by_name(api, "error", error); - g_error_free(error); + g_signal_emit_by_name(api, "error", error); + g_error_free(error); } static void fb_api_cb_attach(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - const gchar *str; - FbApi *api = data; - FbApiMessage *msg; - FbJsonValues *values; - gchar *name; - GError *err = NULL; - GSList *msgs = NULL; - guint i; - JsonNode *root; + const gchar *str; + FbApi *api = data; + FbApiMessage *msg; + FbJsonValues *values; + gchar *name; + GError *err = NULL; + GSList *msgs = NULL; + guint i; + JsonNode *root; - static const gchar *imgexts[] = {".jpg", ".png", ".gif"}; + static const gchar *imgexts[] = {".jpg", ".png", ".gif"}; - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.filename"); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.redirect_uri"); - fb_json_values_update(values, &err); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.filename"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.redirect_uri"); + fb_json_values_update(values, &err); - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return; - ); + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); - msg = fb_api_data_take(api, con); - str = fb_json_values_next_str(values, NULL); - name = g_ascii_strdown(str, -1); + msg = fb_api_data_take(api, con); + str = fb_json_values_next_str(values, NULL); + name = g_ascii_strdown(str, -1); - for (i = 0; i < G_N_ELEMENTS(imgexts); i++) { - if (g_str_has_suffix(name, imgexts[i])) { - msg->flags |= FB_API_MESSAGE_FLAG_IMAGE; - break; - } - } + for (i = 0; i < G_N_ELEMENTS(imgexts); i++) { + if (g_str_has_suffix(name, imgexts[i])) { + msg->flags |= FB_API_MESSAGE_FLAG_IMAGE; + break; + } + } - g_free(name); - msg->text = fb_json_values_next_str_dup(values, NULL); - msgs = g_slist_prepend(msgs, msg); + g_free(name); + msg->text = fb_json_values_next_str_dup(values, NULL); + msgs = g_slist_prepend(msgs, msg); - g_signal_emit_by_name(api, "messages", msgs); - g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); - g_object_unref(values); - json_node_free(root); + g_signal_emit_by_name(api, "messages", msgs); + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_object_unref(values); + json_node_free(root); } static void fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg) { - FbHttpParams *prms; - PurpleHttpConnection *http; + FbHttpParams *prms; + PurpleHttpConnection *http; - prms = fb_http_params_new(); - fb_http_params_set_str(prms, "mid", msgid); - fb_http_params_set_strf(prms, "aid", "%" FB_ID_FORMAT, aid); + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "mid", msgid); + fb_http_params_set_strf(prms, "aid", "%" FB_ID_FORMAT, aid); - http = fb_api_http_req(api, FB_API_URL_ATTACH, "getAttachment", - "messaging.getAttachment", prms, - fb_api_cb_attach); - fb_api_data_set(api, http, msg, (GDestroyNotify) fb_api_message_free); + http = fb_api_http_req(api, FB_API_URL_ATTACH, "getAttachment", + "messaging.getAttachment", prms, + fb_api_cb_attach); + fb_api_data_set(api, http, msg, (GDestroyNotify) fb_api_message_free); } static void fb_api_cb_auth(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - FbApi *api = data; - FbApiPrivate *priv = api->priv; - FbJsonValues *values; - GError *err = NULL; - JsonNode *root; + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.access_token"); - fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid"); - fb_json_values_update(values, &err); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.access_token"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid"); + fb_json_values_update(values, &err); - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return; - ); + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); - g_free(priv->token); - priv->token = fb_json_values_next_str_dup(values, NULL); - priv->uid = fb_json_values_next_int(values, 0); + g_free(priv->token); + priv->token = fb_json_values_next_str_dup(values, NULL); + priv->uid = fb_json_values_next_int(values, 0); - g_signal_emit_by_name(api, "auth"); - g_object_unref(values); - json_node_free(root); + g_signal_emit_by_name(api, "auth"); + g_object_unref(values); + json_node_free(root); } void fb_api_auth(FbApi *api, const gchar *user, const gchar *pass) { - FbHttpParams *prms; + FbHttpParams *prms; - prms = fb_http_params_new(); - fb_http_params_set_str(prms, "email", user); - fb_http_params_set_str(prms, "password", pass); - fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login", - prms, fb_api_cb_auth); + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "email", user); + fb_http_params_set_str(prms, "password", pass); + fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login", + prms, fb_api_cb_auth); } static gchar * fb_api_user_icon_checksum(gchar *icon) { - gchar *csum; - FbHttpParams *prms; + gchar *csum; + FbHttpParams *prms; - if (G_UNLIKELY(icon == NULL)) { - return NULL; - } + if (G_UNLIKELY(icon == NULL)) { + return NULL; + } - prms = fb_http_params_new_parse(icon, TRUE); - csum = fb_http_params_dup_str(prms, "oh", NULL); - fb_http_params_free(prms); + prms = fb_http_params_new_parse(icon, TRUE); + csum = fb_http_params_dup_str(prms, "oh", NULL); + fb_http_params_free(prms); - if (G_UNLIKELY(csum == NULL)) { - /* Revert to the icon URL as the unique checksum */ - csum = g_strdup(icon); - } + if (G_UNLIKELY(csum == NULL)) { + /* Revert to the icon URL as the unique checksum */ + csum = g_strdup(icon); + } - return csum; + return csum; } static void fb_api_cb_contact(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - const gchar *str; - FbApi *api = data; - FbApiUser user; - FbJsonValues *values; - GError *err = NULL; - JsonNode *node; - JsonNode *root; - - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } - - node = fb_json_node_get_nth(root, 0); - - if (node == NULL) { - fb_api_error(api, FB_API_ERROR_GENERAL, - _("Failed to obtain contact information")); - json_node_free(root); - return; - } - - values = fb_json_values_new(node); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.id"); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.name"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.profile_pic_large.uri"); - fb_json_values_update(values, &err); - - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return; - ); - - fb_api_user_reset(&user, FALSE); - str = fb_json_values_next_str(values, "0"); - user.uid = FB_ID_FROM_STR(str); - user.name = fb_json_values_next_str_dup(values, NULL); - user.icon = fb_json_values_next_str_dup(values, NULL); - - user.csum = fb_api_user_icon_checksum(user.icon); - - g_signal_emit_by_name(api, "contact", &user); - fb_api_user_reset(&user, TRUE); - g_object_unref(values); - json_node_free(root); + const gchar *str; + FbApi *api = data; + FbApiUser user; + FbJsonValues *values; + GError *err = NULL; + JsonNode *node; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + node = fb_json_node_get_nth(root, 0); + + if (node == NULL) { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to obtain contact information")); + json_node_free(root); + return; + } + + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.name"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.profile_pic_large.uri"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + fb_api_user_reset(&user, FALSE); + str = fb_json_values_next_str(values, "0"); + user.uid = FB_ID_FROM_STR(str); + user.name = fb_json_values_next_str_dup(values, NULL); + user.icon = fb_json_values_next_str_dup(values, NULL); + + user.csum = fb_api_user_icon_checksum(user.icon); + + g_signal_emit_by_name(api, "contact", &user); + fb_api_user_reset(&user, TRUE); + g_object_unref(values); + json_node_free(root); } void fb_api_contact(FbApi *api, FbId uid) { - JsonBuilder *bldr; + JsonBuilder *bldr; - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_arr_begin(bldr, "0"); - fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid); - fb_json_bldr_arr_end(bldr); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid); + fb_json_bldr_arr_end(bldr); - fb_json_bldr_add_str(bldr, "1", "true"); - fb_api_http_query(api, FB_API_QUERY_CONTACT, bldr, fb_api_cb_contact); + fb_json_bldr_add_str(bldr, "1", "true"); + fb_api_http_query(api, FB_API_QUERY_CONTACT, bldr, fb_api_cb_contact); } static GSList * fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users) { - const gchar *str; - FbApiPrivate *priv = api->priv; - FbApiUser *user; - FbId uid; - FbJsonValues *values; - gboolean is_array; - GError *err = NULL; + const gchar *str; + FbApiPrivate *priv = api->priv; + FbApiUser *user; + FbId uid; + FbJsonValues *values; + gboolean is_array; + GError *err = NULL; - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.represented_profile.id"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.represented_profile.friendship_status"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.structured_name.text"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.hugePictureUrl.uri"); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.represented_profile.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.represented_profile.friendship_status"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.structured_name.text"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.hugePictureUrl.uri"); - is_array = (JSON_NODE_TYPE(root) == JSON_NODE_ARRAY); + is_array = (JSON_NODE_TYPE(root) == JSON_NODE_ARRAY); - if (is_array) { - fb_json_values_set_array(values, FALSE, "$"); - } + if (is_array) { + fb_json_values_set_array(values, FALSE, "$"); + } - while (fb_json_values_update(values, &err)) { - str = fb_json_values_next_str(values, "0"); - uid = FB_ID_FROM_STR(str); - str = fb_json_values_next_str(values, NULL); + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, "0"); + uid = FB_ID_FROM_STR(str); + str = fb_json_values_next_str(values, NULL); - if ((!purple_strequal(str, "ARE_FRIENDS") && - (uid != priv->uid)) || (uid == 0)) - { - if (!is_array) { - break; - } - continue; - } + if ((!purple_strequal(str, "ARE_FRIENDS") && + (uid != priv->uid)) || (uid == 0)) + { + if (!is_array) { + break; + } + continue; + } - user = fb_api_user_dup(NULL, FALSE); - user->uid = uid; - user->name = fb_json_values_next_str_dup(values, NULL); - user->icon = fb_json_values_next_str_dup(values, NULL); + user = fb_api_user_dup(NULL, FALSE); + user->uid = uid; + user->name = fb_json_values_next_str_dup(values, NULL); + user->icon = fb_json_values_next_str_dup(values, NULL); - user->csum = fb_api_user_icon_checksum(user->icon); + user->csum = fb_api_user_icon_checksum(user->icon); - users = g_slist_prepend(users, user); + users = g_slist_prepend(users, user); - if (!is_array) { - break; - } - } + if (!is_array) { + break; + } + } - g_object_unref(values); + g_object_unref(values); - return users; + return users; } /* base64(contact:::) */ static GSList * fb_api_cb_contacts_parse_removed(FbApi *api, JsonNode *node, GSList *users) { - gsize len; - char **split; - char *decoded = (char *) g_base64_decode(json_node_get_string(node), &len); + gsize len; + char **split; + char *decoded = (char *) g_base64_decode(json_node_get_string(node), &len); - g_return_val_if_fail(decoded[len] == '\0', users); - g_return_val_if_fail(len == strlen(decoded), users); - g_return_val_if_fail(g_str_has_prefix(decoded, "contact:"), users); + g_return_val_if_fail(decoded[len] == '\0', users); + g_return_val_if_fail(len == strlen(decoded), users); + g_return_val_if_fail(g_str_has_prefix(decoded, "contact:"), users); - split = g_strsplit_set(decoded, ":", 4); + split = g_strsplit_set(decoded, ":", 4); - g_return_val_if_fail(g_strv_length(split) == 4, users); + g_return_val_if_fail(g_strv_length(split) == 4, users); - users = g_slist_prepend(users, g_strdup(split[2])); + users = g_slist_prepend(users, g_strdup(split[2])); - g_strfreev(split); - g_free(decoded); + g_strfreev(split); + g_free(decoded); - return users; + return users; } static void fb_api_cb_contacts(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - const gchar *cursor; - const gchar *delta_cursor; - FbApi *api = data; - FbApiPrivate *priv = api->priv; - FbJsonValues *values; - gboolean complete; - gboolean is_delta; - GError *err = NULL; - GList *l; - GSList *users = NULL; - JsonNode *root; - JsonNode *croot; - JsonNode *node; - - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } - - croot = fb_json_node_get(root, "$.viewer.messenger_contacts.deltas", NULL); - is_delta = (croot != NULL); - - if (!is_delta) { - croot = fb_json_node_get(root, "$.viewer.messenger_contacts", NULL); - node = fb_json_node_get(croot, "$.nodes", NULL); - users = fb_api_cb_contacts_nodes(api, node, users); - json_node_free(node); - - } else { - GSList *added = NULL; - GSList *removed = NULL; - JsonArray *arr = fb_json_node_get_arr(croot, "$.nodes", NULL); - GList *elms = json_array_get_elements(arr); - - for (l = elms; l != NULL; l = l->next) { - if ((node = fb_json_node_get(l->data, "$.added", NULL))) { - added = fb_api_cb_contacts_nodes(api, node, added); - json_node_free(node); - } - - if ((node = fb_json_node_get(l->data, "$.removed", NULL))) { - removed = fb_api_cb_contacts_parse_removed(api, node, removed); - json_node_free(node); - } - } - - g_signal_emit_by_name(api, "contacts-delta", added, removed); - - g_slist_free_full(added, (GDestroyNotify) fb_api_user_free); - g_slist_free_full(removed, (GDestroyNotify) g_free); - - g_list_free(elms); - json_array_unref(arr); - } - - values = fb_json_values_new(croot); - fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, - "$.page_info.has_next_page"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.page_info.delta_cursor"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.page_info.end_cursor"); - fb_json_values_update(values, NULL); - - complete = !fb_json_values_next_bool(values, FALSE); - - delta_cursor = fb_json_values_next_str(values, NULL); - - cursor = fb_json_values_next_str(values, NULL); - - if (G_UNLIKELY(err == NULL)) { - if (is_delta || complete) { - g_free(priv->contacts_delta); - priv->contacts_delta = g_strdup(is_delta ? cursor : delta_cursor); - } - - if (users || (complete && !is_delta)) { - g_signal_emit_by_name(api, "contacts", users, complete); - } - - if (!complete) { - fb_api_contacts_after(api, cursor); - } - } else { - fb_api_error_emit(api, err); - } - - g_slist_free_full(users, (GDestroyNotify) fb_api_user_free); - g_object_unref(values); - - json_node_free(croot); - json_node_free(root); + const gchar *cursor; + const gchar *delta_cursor; + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + gboolean complete; + gboolean is_delta; + GError *err = NULL; + GList *l; + GSList *users = NULL; + JsonNode *root; + JsonNode *croot; + JsonNode *node; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + croot = fb_json_node_get(root, "$.viewer.messenger_contacts.deltas", NULL); + is_delta = (croot != NULL); + + if (!is_delta) { + croot = fb_json_node_get(root, "$.viewer.messenger_contacts", NULL); + node = fb_json_node_get(croot, "$.nodes", NULL); + users = fb_api_cb_contacts_nodes(api, node, users); + json_node_free(node); + + } else { + GSList *added = NULL; + GSList *removed = NULL; + JsonArray *arr = fb_json_node_get_arr(croot, "$.nodes", NULL); + GList *elms = json_array_get_elements(arr); + + for (l = elms; l != NULL; l = l->next) { + if ((node = fb_json_node_get(l->data, "$.added", NULL))) { + added = fb_api_cb_contacts_nodes(api, node, added); + json_node_free(node); + } + + if ((node = fb_json_node_get(l->data, "$.removed", NULL))) { + removed = fb_api_cb_contacts_parse_removed(api, node, removed); + json_node_free(node); + } + } + + g_signal_emit_by_name(api, "contacts-delta", added, removed); + + g_slist_free_full(added, (GDestroyNotify) fb_api_user_free); + g_slist_free_full(removed, (GDestroyNotify) g_free); + + g_list_free(elms); + json_array_unref(arr); + } + + values = fb_json_values_new(croot); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, + "$.page_info.has_next_page"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.page_info.delta_cursor"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.page_info.end_cursor"); + fb_json_values_update(values, NULL); + + complete = !fb_json_values_next_bool(values, FALSE); + + delta_cursor = fb_json_values_next_str(values, NULL); + + cursor = fb_json_values_next_str(values, NULL); + + if (G_UNLIKELY(err == NULL)) { + if (is_delta || complete) { + g_free(priv->contacts_delta); + priv->contacts_delta = g_strdup(is_delta ? cursor : delta_cursor); + } + + if (users || (complete && !is_delta)) { + g_signal_emit_by_name(api, "contacts", users, complete); + } + + if (!complete) { + fb_api_contacts_after(api, cursor); + } + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(users, (GDestroyNotify) fb_api_user_free); + g_object_unref(values); + + json_node_free(croot); + json_node_free(root); } void fb_api_contacts(FbApi *api) { - FbApiPrivate *priv; - JsonBuilder *bldr; + FbApiPrivate *priv; + JsonBuilder *bldr; - g_return_if_fail(FB_IS_API(api)); - priv = api->priv; + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - if (priv->contacts_delta) { - fb_api_contacts_delta(api, priv->contacts_delta); - return; - } + if (priv->contacts_delta) { + fb_api_contacts_delta(api, priv->contacts_delta); + return; + } - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_arr_begin(bldr, "0"); - fb_json_bldr_add_str(bldr, NULL, "user"); - fb_json_bldr_arr_end(bldr); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_str(bldr, NULL, "user"); + fb_json_bldr_arr_end(bldr); - fb_json_bldr_add_str(bldr, "1", G_STRINGIFY(FB_API_CONTACTS_COUNT)); - fb_api_http_query(api, FB_API_QUERY_CONTACTS, bldr, - fb_api_cb_contacts); + fb_json_bldr_add_str(bldr, "1", G_STRINGIFY(FB_API_CONTACTS_COUNT)); + fb_api_http_query(api, FB_API_QUERY_CONTACTS, bldr, + fb_api_cb_contacts); } static void fb_api_contacts_after(FbApi *api, const gchar *cursor) { - JsonBuilder *bldr; + JsonBuilder *bldr; - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_arr_begin(bldr, "0"); - fb_json_bldr_add_str(bldr, NULL, "user"); - fb_json_bldr_arr_end(bldr); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_str(bldr, NULL, "user"); + fb_json_bldr_arr_end(bldr); - fb_json_bldr_add_str(bldr, "1", cursor); - fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT)); - fb_api_http_query(api, FB_API_QUERY_CONTACTS_AFTER, bldr, - fb_api_cb_contacts); + fb_json_bldr_add_str(bldr, "1", cursor); + fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT)); + fb_api_http_query(api, FB_API_QUERY_CONTACTS_AFTER, bldr, + fb_api_cb_contacts); } void fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor) { - JsonBuilder *bldr; + JsonBuilder *bldr; - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_add_str(bldr, "0", delta_cursor); + fb_json_bldr_add_str(bldr, "0", delta_cursor); - fb_json_bldr_arr_begin(bldr, "1"); - fb_json_bldr_add_str(bldr, NULL, "user"); - fb_json_bldr_arr_end(bldr); + fb_json_bldr_arr_begin(bldr, "1"); + fb_json_bldr_add_str(bldr, NULL, "user"); + fb_json_bldr_arr_end(bldr); - fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT)); - fb_api_http_query(api, FB_API_QUERY_CONTACTS_DELTA, bldr, - fb_api_cb_contacts); + fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT)); + fb_api_http_query(api, FB_API_QUERY_CONTACTS_DELTA, bldr, + fb_api_cb_contacts); } void fb_api_connect(FbApi *api, gboolean invisible) { - FbApiPrivate *priv; + FbApiPrivate *priv; - g_return_if_fail(FB_IS_API(api)); - priv = api->priv; + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - priv->invisible = invisible; - fb_mqtt_open(priv->mqtt, FB_MQTT_HOST, FB_MQTT_PORT); + priv->invisible = invisible; + fb_mqtt_open(priv->mqtt, FB_MQTT_HOST, FB_MQTT_PORT); } void fb_api_disconnect(FbApi *api) { - FbApiPrivate *priv; + FbApiPrivate *priv; - g_return_if_fail(FB_IS_API(api)); - priv = api->priv; + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - fb_mqtt_disconnect(priv->mqtt); + fb_mqtt_disconnect(priv->mqtt); } static void fb_api_message_send(FbApi *api, FbApiMessage *msg) { - const gchar *tpfx; - FbApiPrivate *priv = api->priv; - FbId id; - FbId mid; - gchar *json; - JsonBuilder *bldr; + const gchar *tpfx; + FbApiPrivate *priv = api->priv; + FbId id; + FbId mid; + gchar *json; + JsonBuilder *bldr; - mid = FB_API_MSGID(g_get_real_time() / 1000, g_random_int()); - priv->lastmid = mid; + mid = FB_API_MSGID(g_get_real_time() / 1000, g_random_int()); + priv->lastmid = mid; - if (msg->tid != 0) { - tpfx = "tfbid_"; - id = msg->tid; - } else { - tpfx = ""; - id = msg->uid; - } + if (msg->tid != 0) { + tpfx = "tfbid_"; + id = msg->tid; + } else { + tpfx = ""; + id = msg->uid; + } - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_add_str(bldr, "body", msg->text); - fb_json_bldr_add_strf(bldr, "msgid", "%" FB_ID_FORMAT, mid); - fb_json_bldr_add_strf(bldr, "sender_fbid", "%" FB_ID_FORMAT, priv->uid); - fb_json_bldr_add_strf(bldr, "to", "%s%" FB_ID_FORMAT, tpfx, id); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "body", msg->text); + fb_json_bldr_add_strf(bldr, "msgid", "%" FB_ID_FORMAT, mid); + fb_json_bldr_add_strf(bldr, "sender_fbid", "%" FB_ID_FORMAT, priv->uid); + fb_json_bldr_add_strf(bldr, "to", "%s%" FB_ID_FORMAT, tpfx, id); - json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); - fb_api_publish(api, "/send_message2", "%s", json); - g_free(json); + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/send_message2", "%s", json); + g_free(json); } void fb_api_message(FbApi *api, FbId id, gboolean thread, const gchar *text) { - FbApiMessage *msg; - FbApiPrivate *priv; - gboolean empty; + FbApiMessage *msg; + FbApiPrivate *priv; + gboolean empty; - g_return_if_fail(FB_IS_API(api)); - g_return_if_fail(text != NULL); - priv = api->priv; + g_return_if_fail(FB_IS_API(api)); + g_return_if_fail(text != NULL); + priv = api->priv; - msg = fb_api_message_dup(NULL, FALSE); - msg->text = g_strdup(text); + msg = fb_api_message_dup(NULL, FALSE); + msg->text = g_strdup(text); - if (thread) { - msg->tid = id; - } else { - msg->uid = id; - } + if (thread) { + msg->tid = id; + } else { + msg->uid = id; + } - empty = g_queue_is_empty(priv->msgs); - g_queue_push_tail(priv->msgs, msg); + empty = g_queue_is_empty(priv->msgs); + g_queue_push_tail(priv->msgs, msg); - if (empty && fb_mqtt_connected(priv->mqtt, FALSE)) { - fb_api_message_send(api, msg); - } + if (empty && fb_mqtt_connected(priv->mqtt, FALSE)) { + fb_api_message_send(api, msg); + } } void fb_api_publish(FbApi *api, const gchar *topic, const gchar *format, ...) { - FbApiPrivate *priv; - GByteArray *bytes; - GByteArray *cytes; - gchar *msg; - GError *err = NULL; - va_list ap; + FbApiPrivate *priv; + GByteArray *bytes; + GByteArray *cytes; + gchar *msg; + GError *err = NULL; + va_list ap; - g_return_if_fail(FB_IS_API(api)); - g_return_if_fail(topic != NULL); - g_return_if_fail(format != NULL); - priv = api->priv; + g_return_if_fail(FB_IS_API(api)); + g_return_if_fail(topic != NULL); + g_return_if_fail(format != NULL); + priv = api->priv; - va_start(ap, format); - msg = g_strdup_vprintf(format, ap); - va_end(ap); + va_start(ap, format); + msg = g_strdup_vprintf(format, ap); + va_end(ap); - bytes = g_byte_array_new_take((guint8 *) msg, strlen(msg)); - cytes = fb_util_zlib_deflate(bytes, &err); + bytes = g_byte_array_new_take((guint8 *) msg, strlen(msg)); + cytes = fb_util_zlib_deflate(bytes, &err); - FB_API_ERROR_EMIT(api, err, - g_byte_array_free(bytes, TRUE); - return; - ); + FB_API_ERROR_EMIT(api, err, + g_byte_array_free(bytes, TRUE); + return; + ); - fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, - "Writing message (topic: %s)", - topic); + fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, + "Writing message (topic: %s)", + topic); - fb_mqtt_publish(priv->mqtt, topic, cytes); - g_byte_array_free(cytes, TRUE); - g_byte_array_free(bytes, TRUE); + fb_mqtt_publish(priv->mqtt, topic, cytes); + g_byte_array_free(cytes, TRUE); + g_byte_array_free(bytes, TRUE); } void fb_api_read(FbApi *api, FbId id, gboolean thread) { - const gchar *key; - FbApiPrivate *priv; - gchar *json; - JsonBuilder *bldr; + const gchar *key; + FbApiPrivate *priv; + gchar *json; + JsonBuilder *bldr; - g_return_if_fail(FB_IS_API(api)); - priv = api->priv; + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_add_bool(bldr, "state", TRUE); - fb_json_bldr_add_int(bldr, "syncSeqId", priv->sid); - fb_json_bldr_add_str(bldr, "mark", "read"); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_bool(bldr, "state", TRUE); + fb_json_bldr_add_int(bldr, "syncSeqId", priv->sid); + fb_json_bldr_add_str(bldr, "mark", "read"); - key = thread ? "threadFbId" : "otherUserFbId"; - fb_json_bldr_add_strf(bldr, key, "%" FB_ID_FORMAT, id); + key = thread ? "threadFbId" : "otherUserFbId"; + fb_json_bldr_add_strf(bldr, key, "%" FB_ID_FORMAT, id); - json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); - fb_api_publish(api, "/mark_thread", "%s", json); - g_free(json); + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/mark_thread", "%s", json); + g_free(json); } static GSList * fb_api_cb_unread_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg, GSList *msgs, JsonNode *root, GError **error) { - const gchar *str; - FbApiMessage *dmsg; - FbId id; - FbJsonValues *values; - GError *err = NULL; + const gchar *str; + FbApiMessage *dmsg; + FbId id; + FbJsonValues *values; + GError *err = NULL; - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, - "$.attachment_fbid"); - fb_json_values_set_array(values, FALSE, "$.blob_attachments"); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.attachment_fbid"); + fb_json_values_set_array(values, FALSE, "$.blob_attachments"); - while (fb_json_values_update(values, &err)) { - str = fb_json_values_next_str(values, NULL); - id = FB_ID_FROM_STR(str); - dmsg = fb_api_message_dup(msg, FALSE); - fb_api_attach(api, id, mid, dmsg); - } + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, NULL); + id = FB_ID_FROM_STR(str); + dmsg = fb_api_message_dup(msg, FALSE); + fb_api_attach(api, id, mid, dmsg); + } - if (G_UNLIKELY(err != NULL)) { - g_propagate_error(error, err); - } + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + } - g_object_unref(values); - return msgs; + g_object_unref(values); + return msgs; } static void fb_api_cb_unread_msgs(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - const gchar *body; - const gchar *str; - FbApi *api = data; - FbApiMessage *dmsg; - FbApiMessage msg; - FbId id; - FbId tid; - FbJsonValues *values; - gchar *xma; - GError *err = NULL; - GSList *msgs = NULL; - JsonNode *node; - JsonNode *root; - JsonNode *xode; - - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } - - node = fb_json_node_get_nth(root, 0); - - if (node == NULL) { - fb_api_error(api, FB_API_ERROR_GENERAL, - _("Failed to obtain unread messages")); - json_node_free(root); - return; - } - - values = fb_json_values_new(node); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.thread_key.thread_fbid"); - fb_json_values_update(values, &err); - - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - return; - ); - - fb_api_message_reset(&msg, FALSE); - str = fb_json_values_next_str(values, "0"); - tid = FB_ID_FROM_STR(str); - g_object_unref(values); - - values = fb_json_values_new(node); - fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.unread"); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, - "$.message_sender.messaging_actor.id"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.message.text"); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, - "$.timestamp_precise"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.sticker.id"); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.message_id"); - fb_json_values_set_array(values, FALSE, "$.messages.nodes"); - - while (fb_json_values_update(values, &err)) { - if (!fb_json_values_next_bool(values, FALSE)) { - continue; - } - - str = fb_json_values_next_str(values, "0"); - body = fb_json_values_next_str(values, NULL); - - fb_api_message_reset(&msg, FALSE); - msg.uid = FB_ID_FROM_STR(str); - msg.tid = tid; - - str = fb_json_values_next_str(values, "0"); - msg.tstamp = g_ascii_strtoll(str, NULL, 10); - - if (body != NULL) { - dmsg = fb_api_message_dup(&msg, FALSE); - dmsg->text = g_strdup(body); - msgs = g_slist_prepend(msgs, dmsg); - } - - str = fb_json_values_next_str(values, NULL); - - if (str != NULL) { - dmsg = fb_api_message_dup(&msg, FALSE); - id = FB_ID_FROM_STR(str); - fb_api_sticker(api, id, dmsg); - } - - node = fb_json_values_get_root(values); - xode = fb_json_node_get(node, "$.extensible_attachment", NULL); - - if (xode != NULL) { - xma = fb_api_xma_parse(api, body, xode, &err); - - if (xma != NULL) { - dmsg = fb_api_message_dup(&msg, FALSE); - dmsg->text = xma; - msgs = g_slist_prepend(msgs, dmsg); - } - - json_node_free(xode); - - if (G_UNLIKELY(err != NULL)) { - break; - } - } - - str = fb_json_values_next_str(values, NULL); - - if (str == NULL) { - continue; - } - - msgs = fb_api_cb_unread_parse_attach(api, str, &msg, msgs, - node, &err); - - if (G_UNLIKELY(err != NULL)) { - break; - } - } - - if (G_UNLIKELY(err == NULL)) { - msgs = g_slist_reverse(msgs); - g_signal_emit_by_name(api, "messages", msgs); - } else { - fb_api_error_emit(api, err); - } - - g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); - g_object_unref(values); - json_node_free(root); + const gchar *body; + const gchar *str; + FbApi *api = data; + FbApiMessage *dmsg; + FbApiMessage msg; + FbId id; + FbId tid; + FbJsonValues *values; + gchar *xma; + GError *err = NULL; + GSList *msgs = NULL; + JsonNode *node; + JsonNode *root; + JsonNode *xode; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + node = fb_json_node_get_nth(root, 0); + + if (node == NULL) { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to obtain unread messages")); + json_node_free(root); + return; + } + + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.thread_fbid"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + return; + ); + + fb_api_message_reset(&msg, FALSE); + str = fb_json_values_next_str(values, "0"); + tid = FB_ID_FROM_STR(str); + g_object_unref(values); + + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.unread"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.message_sender.messaging_actor.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.message.text"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.timestamp_precise"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.sticker.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.message_id"); + fb_json_values_set_array(values, FALSE, "$.messages.nodes"); + + while (fb_json_values_update(values, &err)) { + if (!fb_json_values_next_bool(values, FALSE)) { + continue; + } + + str = fb_json_values_next_str(values, "0"); + body = fb_json_values_next_str(values, NULL); + + fb_api_message_reset(&msg, FALSE); + msg.uid = FB_ID_FROM_STR(str); + msg.tid = tid; + + str = fb_json_values_next_str(values, "0"); + msg.tstamp = g_ascii_strtoll(str, NULL, 10); + + if (body != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + dmsg->text = g_strdup(body); + msgs = g_slist_prepend(msgs, dmsg); + } + + str = fb_json_values_next_str(values, NULL); + + if (str != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + id = FB_ID_FROM_STR(str); + fb_api_sticker(api, id, dmsg); + } + + node = fb_json_values_get_root(values); + xode = fb_json_node_get(node, "$.extensible_attachment", NULL); + + if (xode != NULL) { + xma = fb_api_xma_parse(api, body, xode, &err); + + if (xma != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + dmsg->text = xma; + msgs = g_slist_prepend(msgs, dmsg); + } + + json_node_free(xode); + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + continue; + } + + msgs = fb_api_cb_unread_parse_attach(api, str, &msg, msgs, + node, &err); + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + if (G_UNLIKELY(err == NULL)) { + msgs = g_slist_reverse(msgs); + g_signal_emit_by_name(api, "messages", msgs); + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_object_unref(values); + json_node_free(root); } static void fb_api_cb_unread(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - const gchar *id; - FbApi *api = data; - FbJsonValues *values; - GError *err = NULL; - gint64 count; - JsonBuilder *bldr; - JsonNode *root; - - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } - - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.unread_count"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.thread_key.other_user_id"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.thread_key.thread_fbid"); - fb_json_values_set_array(values, FALSE, "$.viewer.message_threads" - ".nodes"); - - while (fb_json_values_update(values, &err)) { - count = fb_json_values_next_int(values, -5); - - if (count < 1) { - continue; - } - - id = fb_json_values_next_str(values, NULL); - - if (id == NULL) { - id = fb_json_values_next_str(values, "0"); - } - - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_arr_begin(bldr, "0"); - fb_json_bldr_add_str(bldr, NULL, id); - fb_json_bldr_arr_end(bldr); - - fb_json_bldr_add_str(bldr, "10", "true"); - fb_json_bldr_add_str(bldr, "11", "true"); - fb_json_bldr_add_int(bldr, "12", count); - fb_json_bldr_add_str(bldr, "13", "false"); - fb_api_http_query(api, FB_API_QUERY_THREAD, bldr, - fb_api_cb_unread_msgs); - } - - if (G_UNLIKELY(err != NULL)) { - fb_api_error_emit(api, err); - } - - g_object_unref(values); - json_node_free(root); + const gchar *id; + FbApi *api = data; + FbJsonValues *values; + GError *err = NULL; + gint64 count; + JsonBuilder *bldr; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.unread_count"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.other_user_id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.thread_fbid"); + fb_json_values_set_array(values, FALSE, "$.viewer.message_threads" + ".nodes"); + + while (fb_json_values_update(values, &err)) { + count = fb_json_values_next_int(values, -5); + + if (count < 1) { + continue; + } + + id = fb_json_values_next_str(values, NULL); + + if (id == NULL) { + id = fb_json_values_next_str(values, "0"); + } + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_str(bldr, NULL, id); + fb_json_bldr_arr_end(bldr); + + fb_json_bldr_add_str(bldr, "10", "true"); + fb_json_bldr_add_str(bldr, "11", "true"); + fb_json_bldr_add_int(bldr, "12", count); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREAD, bldr, + fb_api_cb_unread_msgs); + } + + if (G_UNLIKELY(err != NULL)) { + fb_api_error_emit(api, err); + } + + g_object_unref(values); + json_node_free(root); } void fb_api_unread(FbApi *api) { - FbApiPrivate *priv; - JsonBuilder *bldr; + FbApiPrivate *priv; + JsonBuilder *bldr; - g_return_if_fail(FB_IS_API(api)); - priv = api->priv; + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - if (priv->unread < 1) { - return; - } + if (priv->unread < 1) { + return; + } - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_add_str(bldr, "2", "true"); - fb_json_bldr_add_int(bldr, "1", priv->unread); - fb_json_bldr_add_str(bldr, "12", "true"); - fb_json_bldr_add_str(bldr, "13", "false"); - fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, - fb_api_cb_unread); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "2", "true"); + fb_json_bldr_add_int(bldr, "1", priv->unread); + fb_json_bldr_add_str(bldr, "12", "true"); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, + fb_api_cb_unread); } static void fb_api_cb_sticker(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - FbApi *api = data; - FbApiMessage *msg; - FbJsonValues *values; - GError *err = NULL; - GSList *msgs = NULL; - JsonNode *node; - JsonNode *root; - - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } - - node = fb_json_node_get_nth(root, 0); - values = fb_json_values_new(node); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, - "$.thread_image.uri"); - fb_json_values_update(values, &err); - - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return; - ); - - msg = fb_api_data_take(api, con); - msg->flags |= FB_API_MESSAGE_FLAG_IMAGE; - msg->text = fb_json_values_next_str_dup(values, NULL); - msgs = g_slist_prepend(msgs, msg); - - g_signal_emit_by_name(api, "messages", msgs); - g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); - g_object_unref(values); - json_node_free(root); + FbApi *api = data; + FbApiMessage *msg; + FbJsonValues *values; + GError *err = NULL; + GSList *msgs = NULL; + JsonNode *node; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + node = fb_json_node_get_nth(root, 0); + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.thread_image.uri"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + msg = fb_api_data_take(api, con); + msg->flags |= FB_API_MESSAGE_FLAG_IMAGE; + msg->text = fb_json_values_next_str_dup(values, NULL); + msgs = g_slist_prepend(msgs, msg); + + g_signal_emit_by_name(api, "messages", msgs); + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_object_unref(values); + json_node_free(root); } static void fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg) { - JsonBuilder *bldr; - PurpleHttpConnection *http; + JsonBuilder *bldr; + PurpleHttpConnection *http; - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_arr_begin(bldr, "0"); - fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, sid); - fb_json_bldr_arr_end(bldr); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, sid); + fb_json_bldr_arr_end(bldr); - http = fb_api_http_query(api, FB_API_QUERY_STICKER, bldr, - fb_api_cb_sticker); - fb_api_data_set(api, http, msg, (GDestroyNotify) fb_api_message_free); + http = fb_api_http_query(api, FB_API_QUERY_STICKER, bldr, + fb_api_cb_sticker); + fb_api_data_set(api, http, msg, (GDestroyNotify) fb_api_message_free); } static gboolean fb_api_thread_parse(FbApi *api, FbApiThread *thrd, JsonNode *root, GError **error) { - const gchar *str; - FbApiPrivate *priv = api->priv; - FbApiUser *user; - FbId uid; - FbJsonValues *values; - gboolean haself = FALSE; - guint num_users = 0; - GError *err = NULL; - - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.thread_key.thread_fbid"); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.name"); - fb_json_values_update(values, &err); - - if (G_UNLIKELY(err != NULL)) { - g_propagate_error(error, err); - g_object_unref(values); - return FALSE; - } - - str = fb_json_values_next_str(values, NULL); - - if (str == NULL) { - g_object_unref(values); - return FALSE; - } - - thrd->tid = FB_ID_FROM_STR(str); - thrd->topic = fb_json_values_next_str_dup(values, NULL); - g_object_unref(values); - - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, - "$.messaging_actor.id"); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, - "$.messaging_actor.name"); - fb_json_values_set_array(values, TRUE, "$.all_participants.nodes"); - - while (fb_json_values_update(values, &err)) { - str = fb_json_values_next_str(values, "0"); - uid = FB_ID_FROM_STR(str); - num_users++; - - if (uid != priv->uid) { - user = fb_api_user_dup(NULL, FALSE); - user->uid = uid; - user->name = fb_json_values_next_str_dup(values, NULL); - thrd->users = g_slist_prepend(thrd->users, user); - } else { - haself = TRUE; - } - } - - if (G_UNLIKELY(err != NULL)) { - g_propagate_error(error, err); - fb_api_thread_reset(thrd, TRUE); - g_object_unref(values); - return FALSE; - } - - if (num_users < 2 || !haself) { - g_object_unref(values); - return FALSE; - } - - g_object_unref(values); - return TRUE; + const gchar *str; + FbApiPrivate *priv = api->priv; + FbApiUser *user; + FbId uid; + FbJsonValues *values; + gboolean haself = FALSE; + guint num_users = 0; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.thread_fbid"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.name"); + fb_json_values_update(values, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + g_object_unref(values); + return FALSE; + } + + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + g_object_unref(values); + return FALSE; + } + + thrd->tid = FB_ID_FROM_STR(str); + thrd->topic = fb_json_values_next_str_dup(values, NULL); + g_object_unref(values); + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.messaging_actor.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.messaging_actor.name"); + fb_json_values_set_array(values, TRUE, "$.all_participants.nodes"); + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, "0"); + uid = FB_ID_FROM_STR(str); + num_users++; + + if (uid != priv->uid) { + user = fb_api_user_dup(NULL, FALSE); + user->uid = uid; + user->name = fb_json_values_next_str_dup(values, NULL); + thrd->users = g_slist_prepend(thrd->users, user); + } else { + haself = TRUE; + } + } + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + fb_api_thread_reset(thrd, TRUE); + g_object_unref(values); + return FALSE; + } + + if (num_users < 2 || !haself) { + g_object_unref(values); + return FALSE; + } + + g_object_unref(values); + return TRUE; } static void fb_api_cb_thread(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - FbApi *api = data; - FbApiThread thrd; - GError *err = NULL; - JsonNode *node; - JsonNode *root; - - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } - - node = fb_json_node_get_nth(root, 0); - - if (node == NULL) { - fb_api_error(api, FB_API_ERROR_GENERAL, - _("Failed to obtain thread information")); - json_node_free(root); - return; - } - - fb_api_thread_reset(&thrd, FALSE); - - if (!fb_api_thread_parse(api, &thrd, node, &err)) { - if (G_LIKELY(err == NULL)) { - if (thrd.tid) { - g_signal_emit_by_name(api, "thread-kicked", &thrd); - } else { - fb_api_error(api, FB_API_ERROR_GENERAL, - _("Failed to parse thread information")); - } - } else { - fb_api_error_emit(api, err); - } - } else { - g_signal_emit_by_name(api, "thread", &thrd); - } - - fb_api_thread_reset(&thrd, TRUE); - json_node_free(root); + FbApi *api = data; + FbApiThread thrd; + GError *err = NULL; + JsonNode *node; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + node = fb_json_node_get_nth(root, 0); + + if (node == NULL) { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to obtain thread information")); + json_node_free(root); + return; + } + + fb_api_thread_reset(&thrd, FALSE); + + if (!fb_api_thread_parse(api, &thrd, node, &err)) { + if (G_LIKELY(err == NULL)) { + if (thrd.tid) { + g_signal_emit_by_name(api, "thread-kicked", &thrd); + } else { + fb_api_error(api, FB_API_ERROR_GENERAL, + _("Failed to parse thread information")); + } + } else { + fb_api_error_emit(api, err); + } + } else { + g_signal_emit_by_name(api, "thread", &thrd); + } + + fb_api_thread_reset(&thrd, TRUE); + json_node_free(root); } void fb_api_thread(FbApi *api, FbId tid) { - JsonBuilder *bldr; + JsonBuilder *bldr; - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_arr_begin(bldr, "0"); - fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, tid); - fb_json_bldr_arr_end(bldr); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, tid); + fb_json_bldr_arr_end(bldr); - fb_json_bldr_add_str(bldr, "10", "false"); - fb_json_bldr_add_str(bldr, "11", "false"); - fb_json_bldr_add_str(bldr, "13", "false"); - fb_api_http_query(api, FB_API_QUERY_THREAD, bldr, fb_api_cb_thread); + fb_json_bldr_add_str(bldr, "10", "false"); + fb_json_bldr_add_str(bldr, "11", "false"); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREAD, bldr, fb_api_cb_thread); } static void fb_api_cb_thread_create(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - const gchar *str; - FbApi *api = data; - FbId tid; - FbJsonValues *values; - GError *err = NULL; - JsonNode *root; + const gchar *str; + FbApi *api = data; + FbId tid; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } - values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.id"); - fb_json_values_update(values, &err); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.id"); + fb_json_values_update(values, &err); - FB_API_ERROR_EMIT(api, err, - g_object_unref(values); - json_node_free(root); - return; - ); + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); - str = fb_json_values_next_str(values, "0"); - tid = FB_ID_FROM_STR(str); - g_signal_emit_by_name(api, "thread-create", tid); + str = fb_json_values_next_str(values, "0"); + tid = FB_ID_FROM_STR(str); + g_signal_emit_by_name(api, "thread-create", tid); - g_object_unref(values); - json_node_free(root); + g_object_unref(values); + json_node_free(root); } void fb_api_thread_create(FbApi *api, GSList *uids) { - FbApiPrivate *priv; - FbHttpParams *prms; - FbId *uid; - gchar *json; - GSList *l; - JsonBuilder *bldr; - - g_return_if_fail(FB_IS_API(api)); - g_warn_if_fail(g_slist_length(uids) > 1); - priv = api->priv; - - bldr = fb_json_bldr_new(JSON_NODE_ARRAY); - fb_json_bldr_obj_begin(bldr, NULL); - fb_json_bldr_add_str(bldr, "type", "id"); - fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, priv->uid); - fb_json_bldr_obj_end(bldr); - - for (l = uids; l != NULL; l = l->next) { - uid = l->data; - fb_json_bldr_obj_begin(bldr, NULL); - fb_json_bldr_add_str(bldr, "type", "id"); - fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, *uid); - fb_json_bldr_obj_end(bldr); - } - - json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); - prms = fb_http_params_new(); - fb_http_params_set_str(prms, "recipients", json); - fb_api_http_req(api, FB_API_URL_THREADS, "createGroup", "POST", - prms, fb_api_cb_thread_create); - g_free(json); + FbApiPrivate *priv; + FbHttpParams *prms; + FbId *uid; + gchar *json; + GSList *l; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + g_warn_if_fail(g_slist_length(uids) > 1); + priv = api->priv; + + bldr = fb_json_bldr_new(JSON_NODE_ARRAY); + fb_json_bldr_obj_begin(bldr, NULL); + fb_json_bldr_add_str(bldr, "type", "id"); + fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, priv->uid); + fb_json_bldr_obj_end(bldr); + + for (l = uids; l != NULL; l = l->next) { + uid = l->data; + fb_json_bldr_obj_begin(bldr, NULL); + fb_json_bldr_add_str(bldr, "type", "id"); + fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, *uid); + fb_json_bldr_obj_end(bldr); + } + + json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "recipients", json); + fb_api_http_req(api, FB_API_URL_THREADS, "createGroup", "POST", + prms, fb_api_cb_thread_create); + g_free(json); } void fb_api_thread_invite(FbApi *api, FbId tid, FbId uid) { - FbHttpParams *prms; - gchar *json; - JsonBuilder *bldr; + FbHttpParams *prms; + gchar *json; + JsonBuilder *bldr; - bldr = fb_json_bldr_new(JSON_NODE_ARRAY); - fb_json_bldr_obj_begin(bldr, NULL); - fb_json_bldr_add_str(bldr, "type", "id"); - fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, uid); - fb_json_bldr_obj_end(bldr); - json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); + bldr = fb_json_bldr_new(JSON_NODE_ARRAY); + fb_json_bldr_obj_begin(bldr, NULL); + fb_json_bldr_add_str(bldr, "type", "id"); + fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, uid); + fb_json_bldr_obj_end(bldr); + json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); - prms = fb_http_params_new(); - fb_http_params_set_str(prms, "to", json); - fb_http_params_set_strf(prms, "id", "t_%" FB_ID_FORMAT, tid); - fb_api_http_req(api, FB_API_URL_PARTS, "addMembers", "POST", - prms, fb_api_cb_http_bool); - g_free(json); + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "to", json); + fb_http_params_set_strf(prms, "id", "t_%" FB_ID_FORMAT, tid); + fb_api_http_req(api, FB_API_URL_PARTS, "addMembers", "POST", + prms, fb_api_cb_http_bool); + g_free(json); } void fb_api_thread_remove(FbApi *api, FbId tid, FbId uid) { - FbApiPrivate *priv; - FbHttpParams *prms; - gchar *json; - JsonBuilder *bldr; + FbApiPrivate *priv; + FbHttpParams *prms; + gchar *json; + JsonBuilder *bldr; - g_return_if_fail(FB_IS_API(api)); - priv = api->priv; + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - prms = fb_http_params_new(); - fb_http_params_set_strf(prms, "id", "t_%" FB_ID_FORMAT, tid); + prms = fb_http_params_new(); + fb_http_params_set_strf(prms, "id", "t_%" FB_ID_FORMAT, tid); - if (uid == 0) { - uid = priv->uid; - } + if (uid == 0) { + uid = priv->uid; + } - if (uid != priv->uid) { - bldr = fb_json_bldr_new(JSON_NODE_ARRAY); - fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid); - json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); - fb_http_params_set_str(prms, "to", json); - g_free(json); - } + if (uid != priv->uid) { + bldr = fb_json_bldr_new(JSON_NODE_ARRAY); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid); + json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); + fb_http_params_set_str(prms, "to", json); + g_free(json); + } - fb_api_http_req(api, FB_API_URL_PARTS, "removeMembers", "DELETE", - prms, fb_api_cb_http_bool); + fb_api_http_req(api, FB_API_URL_PARTS, "removeMembers", "DELETE", + prms, fb_api_cb_http_bool); } void fb_api_thread_topic(FbApi *api, FbId tid, const gchar *topic) { - FbHttpParams *prms; + FbHttpParams *prms; - prms = fb_http_params_new(); - fb_http_params_set_str(prms, "name", topic); - fb_http_params_set_int(prms, "tid", tid); - fb_api_http_req(api, FB_API_URL_TOPIC, "setThreadName", - "messaging.setthreadname", prms, - fb_api_cb_http_bool); + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "name", topic); + fb_http_params_set_int(prms, "tid", tid); + fb_api_http_req(api, FB_API_URL_TOPIC, "setThreadName", + "messaging.setthreadname", prms, + fb_api_cb_http_bool); } static void fb_api_cb_threads(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { - FbApi *api = data; - FbApiThread *dthrd; - FbApiThread thrd; - GError *err = NULL; - GList *elms; - GList *l; - GSList *thrds = NULL; - JsonArray *arr; - JsonNode *root; - - if (!fb_api_http_chk(api, con, res, &root)) { - return; - } - - arr = fb_json_node_get_arr(root, "$.viewer.message_threads.nodes", - &err); - FB_API_ERROR_EMIT(api, err, - json_node_free(root); - return; - ); - - elms = json_array_get_elements(arr); - - for (l = elms; l != NULL; l = l->next) { - fb_api_thread_reset(&thrd, FALSE); - - if (fb_api_thread_parse(api, &thrd, l->data, &err)) { - dthrd = fb_api_thread_dup(&thrd, FALSE); - thrds = g_slist_prepend(thrds, dthrd); - } else { - fb_api_thread_reset(&thrd, TRUE); - } - - if (G_UNLIKELY(err != NULL)) { - break; - } - } - - if (G_LIKELY(err == NULL)) { - thrds = g_slist_reverse(thrds); - g_signal_emit_by_name(api, "threads", thrds); - } else { - fb_api_error_emit(api, err); - } - - g_slist_free_full(thrds, (GDestroyNotify) fb_api_thread_free); - g_list_free(elms); - json_array_unref(arr); - json_node_free(root); + FbApi *api = data; + FbApiThread *dthrd; + FbApiThread thrd; + GError *err = NULL; + GList *elms; + GList *l; + GSList *thrds = NULL; + JsonArray *arr; + JsonNode *root; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + arr = fb_json_node_get_arr(root, "$.viewer.message_threads.nodes", + &err); + FB_API_ERROR_EMIT(api, err, + json_node_free(root); + return; + ); + + elms = json_array_get_elements(arr); + + for (l = elms; l != NULL; l = l->next) { + fb_api_thread_reset(&thrd, FALSE); + + if (fb_api_thread_parse(api, &thrd, l->data, &err)) { + dthrd = fb_api_thread_dup(&thrd, FALSE); + thrds = g_slist_prepend(thrds, dthrd); + } else { + fb_api_thread_reset(&thrd, TRUE); + } + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + if (G_LIKELY(err == NULL)) { + thrds = g_slist_reverse(thrds); + g_signal_emit_by_name(api, "threads", thrds); + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(thrds, (GDestroyNotify) fb_api_thread_free); + g_list_free(elms); + json_array_unref(arr); + json_node_free(root); } void fb_api_threads(FbApi *api) { - JsonBuilder *bldr; + JsonBuilder *bldr; - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_add_str(bldr, "2", "true"); - fb_json_bldr_add_str(bldr, "12", "false"); - fb_json_bldr_add_str(bldr, "13", "false"); - fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, fb_api_cb_threads); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "2", "true"); + fb_json_bldr_add_str(bldr, "12", "false"); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, fb_api_cb_threads); } void fb_api_typing(FbApi *api, FbId uid, gboolean state) { - gchar *json; - JsonBuilder *bldr; + gchar *json; + JsonBuilder *bldr; - bldr = fb_json_bldr_new(JSON_NODE_OBJECT); - fb_json_bldr_add_int(bldr, "state", state != 0); - fb_json_bldr_add_strf(bldr, "to", "%" FB_ID_FORMAT, uid); + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_int(bldr, "state", state != 0); + fb_json_bldr_add_strf(bldr, "to", "%" FB_ID_FORMAT, uid); - json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); - fb_api_publish(api, "/typing", "%s", json); - g_free(json); + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/typing", "%s", json); + g_free(json); } FbApiEvent * fb_api_event_dup(const FbApiEvent *event, gboolean deep) { - FbApiEvent *ret; + FbApiEvent *ret; - if (event == NULL) { - return g_new0(FbApiEvent, 1); - } + if (event == NULL) { + return g_new0(FbApiEvent, 1); + } - ret = g_memdup2(event, sizeof *event); + ret = g_memdup2(event, sizeof *event); - if (deep) { - ret->text = g_strdup(event->text); - } + if (deep) { + ret->text = g_strdup(event->text); + } - return ret; + return ret; } void fb_api_event_reset(FbApiEvent *event, gboolean deep) { - g_return_if_fail(event != NULL); + g_return_if_fail(event != NULL); - if (deep) { - g_free(event->text); - } + if (deep) { + g_free(event->text); + } - memset(event, 0, sizeof *event); + memset(event, 0, sizeof *event); } void fb_api_event_free(FbApiEvent *event) { - if (G_LIKELY(event != NULL)) { - g_free(event->text); - g_free(event); - } + if (G_LIKELY(event != NULL)) { + g_free(event->text); + g_free(event); + } } FbApiMessage * fb_api_message_dup(const FbApiMessage *msg, gboolean deep) { - FbApiMessage *ret; + FbApiMessage *ret; - if (msg == NULL) { - return g_new0(FbApiMessage, 1); - } + if (msg == NULL) { + return g_new0(FbApiMessage, 1); + } - ret = g_memdup2(msg, sizeof *msg); + ret = g_memdup2(msg, sizeof *msg); - if (deep) { - ret->text = g_strdup(msg->text); - } + if (deep) { + ret->text = g_strdup(msg->text); + } - return ret; + return ret; } void fb_api_message_reset(FbApiMessage *msg, gboolean deep) { - g_return_if_fail(msg != NULL); + g_return_if_fail(msg != NULL); - if (deep) { - g_free(msg->text); - } + if (deep) { + g_free(msg->text); + } - memset(msg, 0, sizeof *msg); + memset(msg, 0, sizeof *msg); } void fb_api_message_free(FbApiMessage *msg) { - if (G_LIKELY(msg != NULL)) { - g_free(msg->text); - g_free(msg); - } + if (G_LIKELY(msg != NULL)) { + g_free(msg->text); + g_free(msg); + } } FbApiPresence * fb_api_presence_dup(const FbApiPresence *pres) { - if (pres == NULL) { - return g_new0(FbApiPresence, 1); - } + if (pres == NULL) { + return g_new0(FbApiPresence, 1); + } - return g_memdup2(pres, sizeof *pres); + return g_memdup2(pres, sizeof *pres); } void fb_api_presence_reset(FbApiPresence *pres) { - g_return_if_fail(pres != NULL); - memset(pres, 0, sizeof *pres); + g_return_if_fail(pres != NULL); + memset(pres, 0, sizeof *pres); } void fb_api_presence_free(FbApiPresence *pres) { - if (G_LIKELY(pres != NULL)) { - g_free(pres); - } + if (G_LIKELY(pres != NULL)) { + g_free(pres); + } } FbApiThread * fb_api_thread_dup(const FbApiThread *thrd, gboolean deep) { - FbApiThread *ret; - FbApiUser *user; - GSList *l; + FbApiThread *ret; + FbApiUser *user; + GSList *l; - if (thrd == NULL) { - return g_new0(FbApiThread, 1); - } + if (thrd == NULL) { + return g_new0(FbApiThread, 1); + } - ret = g_memdup2(thrd, sizeof *thrd); + ret = g_memdup2(thrd, sizeof *thrd); - if (deep) { - ret->users = NULL; + if (deep) { + ret->users = NULL; - for (l = thrd->users; l != NULL; l = l->next) { - user = fb_api_user_dup(l->data, TRUE); - ret->users = g_slist_prepend(ret->users, user); - } + for (l = thrd->users; l != NULL; l = l->next) { + user = fb_api_user_dup(l->data, TRUE); + ret->users = g_slist_prepend(ret->users, user); + } - ret->topic = g_strdup(thrd->topic); - ret->users = g_slist_reverse(ret->users); - } + ret->topic = g_strdup(thrd->topic); + ret->users = g_slist_reverse(ret->users); + } - return ret; + return ret; } void fb_api_thread_reset(FbApiThread *thrd, gboolean deep) { - g_return_if_fail(thrd != NULL); + g_return_if_fail(thrd != NULL); - if (deep) { - g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free); - g_free(thrd->topic); - } + if (deep) { + g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free); + g_free(thrd->topic); + } - memset(thrd, 0, sizeof *thrd); + memset(thrd, 0, sizeof *thrd); } void fb_api_thread_free(FbApiThread *thrd) { - if (G_LIKELY(thrd != NULL)) { - g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free); - g_free(thrd->topic); - g_free(thrd); - } + if (G_LIKELY(thrd != NULL)) { + g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free); + g_free(thrd->topic); + g_free(thrd); + } } FbApiTyping * fb_api_typing_dup(const FbApiTyping *typg) { - if (typg == NULL) { - return g_new0(FbApiTyping, 1); - } + if (typg == NULL) { + return g_new0(FbApiTyping, 1); + } - return g_memdup2(typg, sizeof *typg); + return g_memdup2(typg, sizeof *typg); } void fb_api_typing_reset(FbApiTyping *typg) { - g_return_if_fail(typg != NULL); - memset(typg, 0, sizeof *typg); + g_return_if_fail(typg != NULL); + memset(typg, 0, sizeof *typg); } void fb_api_typing_free(FbApiTyping *typg) { - if (G_LIKELY(typg != NULL)) { - g_free(typg); - } + if (G_LIKELY(typg != NULL)) { + g_free(typg); + } } FbApiUser * fb_api_user_dup(const FbApiUser *user, gboolean deep) { - FbApiUser *ret; + FbApiUser *ret; - if (user == NULL) { - return g_new0(FbApiUser, 1); - } + if (user == NULL) { + return g_new0(FbApiUser, 1); + } - ret = g_memdup2(user, sizeof *user); + ret = g_memdup2(user, sizeof *user); - if (deep) { - ret->name = g_strdup(user->name); - ret->icon = g_strdup(user->icon); - ret->csum = g_strdup(user->csum); - } + if (deep) { + ret->name = g_strdup(user->name); + ret->icon = g_strdup(user->icon); + ret->csum = g_strdup(user->csum); + } - return ret; + return ret; } void fb_api_user_reset(FbApiUser *user, gboolean deep) { - g_return_if_fail(user != NULL); + g_return_if_fail(user != NULL); - if (deep) { - g_free(user->name); - g_free(user->icon); - g_free(user->csum); - } + if (deep) { + g_free(user->name); + g_free(user->icon); + g_free(user->csum); + } - memset(user, 0, sizeof *user); + memset(user, 0, sizeof *user); } void fb_api_user_free(FbApiUser *user) { - if (G_LIKELY(user != NULL)) { - g_free(user->name); - g_free(user->icon); - g_free(user->csum); - g_free(user); - } + if (G_LIKELY(user != NULL)) { + g_free(user->name); + g_free(user->icon); + g_free(user->csum); + g_free(user); + } } From 931a916cc0a6d7cae3503cd6f584881810681a38 Mon Sep 17 00:00:00 2001 From: James Carthew Date: Sun, 7 Apr 2024 15:27:17 +0930 Subject: [PATCH 14/26] Updated Facebook API to start merging 2FA Code from Bitlbee. --- pidgin/libpurple/protocols/facebook/api.c | 235 +- pidgin/libpurple/protocols/facebook/api.h | 232 +- .../libpurple/protocols/facebook/facebook.c | 2608 ++++++++--------- 3 files changed, 1643 insertions(+), 1432 deletions(-) diff --git a/pidgin/libpurple/protocols/facebook/api.c b/pidgin/libpurple/protocols/facebook/api.c index 1abc9381..462129bd 100644 --- a/pidgin/libpurple/protocols/facebook/api.c +++ b/pidgin/libpurple/protocols/facebook/api.c @@ -33,6 +33,7 @@ #include "util.h" typedef struct _FbApiData FbApiData; +typedef struct _FbApiPreloginData FbApiPreloginData; enum { @@ -44,6 +45,8 @@ enum PROP_STOKEN, PROP_TOKEN, PROP_UID, + PROP_TWEAK, + PROP_WORK, PROP_N }; @@ -69,6 +72,11 @@ struct _FbApiPrivate guint unread; FbId lastmid; gchar *contacts_delta; + int tweak; + gboolean is_work; + gboolean need_work_switch; + gchar *sso_verifier; + FbId work_community_id; }; struct _FbApiData @@ -77,6 +85,13 @@ struct _FbApiData GDestroyNotify func; }; +struct _FbApiPreloginData +{ + FbApi *api; + gchar *user; + gchar *pass; +}; + static void fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg); @@ -94,6 +109,27 @@ fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor); G_DEFINE_TYPE_WITH_CODE(FbApi, fb_api, G_TYPE_OBJECT, G_ADD_PRIVATE(FbApi)); +static const gchar *agents[] = { + FB_API_AGENT, + FB_API_AGENT_BASE " " "[FBAN/Orca-Android;FBAV/64.0.0.5.83;FBPN/com.facebook.orca;FBLC/en_US;FBBV/26040814]", + FB_API_AGENT_BASE " " "[FBAN/Orca-Android;FBAV/109.0.0.17.70;FBBV/52182662]", + FB_API_AGENT_BASE " " "[FBAN/Orca-Android;FBAV/109.0.0.17.70;FBPN/com.facebook.orca;FBLC/en_US;FBBV/52182662]", + NULL, +}; + +static const gchar * +fb_api_get_agent_string(int tweak, gboolean mqtt) +{ + gboolean http_only = tweak & 4; + gboolean mqtt_only = tweak & 8; + + if (tweak <= 0 || tweak > 15 || (http_only && mqtt) || (mqtt_only && !mqtt)) { + return agents[0]; + } + + return agents[tweak & 3]; +} + static void fb_api_set_property(GObject *obj, guint prop, const GValue *val, GParamSpec *pspec) @@ -123,6 +159,13 @@ fb_api_set_property(GObject *obj, guint prop, const GValue *val, case PROP_UID: priv->uid = g_value_get_int64(val); break; + case PROP_TWEAK: + priv->tweak = g_value_get_int(val); +// fb_http_set_agent(priv->http, fb_api_get_agent_string(priv->tweak, 0)); + break; + case PROP_WORK: + priv->is_work = g_value_get_boolean(val); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); @@ -154,6 +197,12 @@ fb_api_get_property(GObject *obj, guint prop, GValue *val, GParamSpec *pspec) case PROP_UID: g_value_set_int64(val, priv->uid); break; + case PROP_TWEAK: + g_value_set_int(val, priv->tweak); + break; + case PROP_WORK: + g_value_set_boolean(val, priv->is_work); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); @@ -279,6 +328,26 @@ fb_api_class_init(FbApiClass *klass) "User identifier", 0, G_MAXINT64, 0, G_PARAM_READWRITE); + + /** + * FbApi:tweak: + */ + props[PROP_TWEAK] = g_param_spec_int( + "tweak", + "Tweak", + "", + 0, G_MAXINT, 0, + G_PARAM_READWRITE); + + /** + * FbApi:work: + */ + props[PROP_WORK] = g_param_spec_boolean( + "work", + "Work", + "", + FALSE, + G_PARAM_READWRITE); g_object_class_install_properties(gklass, PROP_N, props); /** @@ -517,6 +586,22 @@ fb_api_class_init(FbApiClass *klass) fb_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * FbApi::work-sso-login: + * @api: The #FbApi. + * + * Emitted when user interaction is required to continue SAML SSO login + */ + + g_signal_new("work-sso-login", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); } static void @@ -788,8 +873,7 @@ fb_api_http_req(FbApi *api, const gchar *url, const gchar *name, } g_string_append(gstr, FB_API_SECRET); - data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, - gstr->len); + data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, gstr->len); fb_http_params_set_str(params, "sig", data); g_string_free(gstr, TRUE); g_list_free(keys); @@ -974,6 +1058,8 @@ fb_api_cb_mqtt_open(FbMqtt *mqtt, gpointer data) /* Write the list of topics to subscribe */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_LIST, 14, 12); fb_thrift_write_list(thft, FB_THRIFT_TYPE_I32, 0); + + /* Write the STOP for the struct */ fb_thrift_write_stop(thft); /* Write the token */ @@ -1013,8 +1099,7 @@ fb_api_connect_queue(FbApi *api) fb_json_bldr_add_str(bldr, "encoding", "JSON"); if (priv->stoken == NULL) { - fb_json_bldr_add_int(bldr, "initial_titan_sequence_id", - priv->sid); + fb_json_bldr_add_int(bldr, "initial_titan_sequence_id", priv->sid); fb_json_bldr_add_str(bldr, "device_id", priv->did); fb_json_bldr_add_int(bldr, "entity_fbid", priv->uid); @@ -1034,8 +1119,7 @@ fb_api_connect_queue(FbApi *api) fb_json_bldr_obj_end(bldr); json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); - fb_api_publish(api, "/messenger_sync_create_queue", "%s", - json); + fb_api_publish(api, "/messenger_sync_create_queue", "%s", json); g_free(json); return; } @@ -1060,8 +1144,7 @@ fb_api_connect_queue(FbApi *api) } static void -fb_api_cb_seqid(PurpleHttpConnection *con, PurpleHttpResponse *res, - gpointer data) +fb_api_cb_seqid(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { const gchar *str; FbApi *api = data; @@ -1075,7 +1158,7 @@ fb_api_cb_seqid(PurpleHttpConnection *con, PurpleHttpResponse *res, } values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.viewer.message_threads.sync_sequence_id"); fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.viewer.message_threads.unread_count"); @@ -1139,8 +1222,7 @@ fb_api_cb_mqtt_connect(FbMqtt *mqtt, gpointer data) if (priv->sid == 0) { bldr = fb_json_bldr_new(JSON_NODE_OBJECT); fb_json_bldr_add_str(bldr, "1", "0"); - fb_api_http_query(api, FB_API_QUERY_SEQ_ID, bldr, - fb_api_cb_seqid); + fb_api_http_query(api, FB_API_QUERY_SEQ_ID, bldr, fb_api_cb_seqid); } else { fb_api_connect_queue(api); } @@ -1200,8 +1282,7 @@ fb_api_event_parse(FbApi *api, FbApiEvent *event, GSList *events, }; values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.log_message_type"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.log_message_type"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.author"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.log_message_data.name"); @@ -1494,7 +1575,6 @@ fb_api_message_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg, return msgs; } - static GSList * fb_api_cb_publish_ms_new_message(FbApi *api, JsonNode *root, GSList *msgs, GError **error); @@ -1512,10 +1592,12 @@ fb_api_cb_publish_mst(FbThrift *thft, GError **error) FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0)); FB_API_TCHK(type == FB_THRIFT_TYPE_STRING); - // FB_API_TCHK(id == 2); + FB_API_TCHK(id ==1 || id == 2); FB_API_TCHK(fb_thrift_read_str(thft, NULL)); FB_API_TCHK(fb_thrift_read_stop(thft)); } + + return; } static void @@ -1565,8 +1647,7 @@ fb_api_cb_publish_ms(FbApi *api, GByteArray *pload) } values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.lastIssuedSeqId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.lastIssuedSeqId"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.syncToken"); fb_json_values_update(values, &err); @@ -1721,8 +1802,8 @@ fb_api_cb_publish_ms_new_message(FbApi *api, JsonNode *root, GSList *msgs, GErro } node = fb_json_values_get_root(values); - msgs = fb_api_message_parse_attach(api, str, &msg, msgs, body, - node, &err); + msgs = fb_api_message_parse_attach(api, str, &msg, msgs, body, node, + &err); if (G_UNLIKELY(err != NULL)) { g_propagate_error(error, err); @@ -1750,33 +1831,33 @@ fb_api_cb_publish_ms_event(FbApi *api, JsonNode *root, GSList *events, FbApiEven "$.messageMetadata.actorFbId"); switch (type) { - case FB_API_EVENT_TYPE_THREAD_TOPIC: - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.name"); - break; + case FB_API_EVENT_TYPE_THREAD_TOPIC: + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.name"); + break; - case FB_API_EVENT_TYPE_THREAD_USER_ADDED: - values_inner = fb_json_values_new(root); + case FB_API_EVENT_TYPE_THREAD_USER_ADDED: + values_inner = fb_json_values_new(root); - fb_json_values_add(values_inner, FB_JSON_TYPE_INT, FALSE, - "$.userFbId"); + fb_json_values_add(values_inner, FB_JSON_TYPE_INT, FALSE, + "$.userFbId"); - /* use the text field for the full name */ - fb_json_values_add(values_inner, FB_JSON_TYPE_STR, FALSE, - "$.fullName"); + /* use the text field for the full name */ + fb_json_values_add(values_inner, FB_JSON_TYPE_STR, FALSE, + "$.fullName"); - fb_json_values_set_array(values_inner, FALSE, - "$.addedParticipants"); - break; + fb_json_values_set_array(values_inner, FALSE, + "$.addedParticipants"); + break; - case FB_API_EVENT_TYPE_THREAD_USER_REMOVED: - fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, - "$.leftParticipantFbId"); + case FB_API_EVENT_TYPE_THREAD_USER_REMOVED: + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.leftParticipantFbId"); - /* use the text field for the kick message */ - fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, - "$.messageMetadata.adminText"); - break; + /* use the text field for the kick message */ + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.messageMetadata.adminText"); + break; } fb_json_values_update(values, &err); @@ -1973,7 +2054,7 @@ fb_api_cb_mqtt_publish(FbMqtt *mqtt, const gchar *topic, GByteArray *pload, fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, "Reading message (topic: %s)", - topic); + topic); for (i = 0; i < G_N_ELEMENTS(parsers); i++) { if (g_ascii_strcasecmp(topic, parsers[i].topic) == 0) { @@ -2082,8 +2163,7 @@ fb_api_error_emit(FbApi *api, GError *error) } static void -fb_api_cb_attach(PurpleHttpConnection *con, PurpleHttpResponse *res, - gpointer data) +fb_api_cb_attach(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { const gchar *str; FbApi *api = data; @@ -2166,7 +2246,14 @@ fb_api_cb_auth(PurpleHttpConnection *con, PurpleHttpResponse *res, values = fb_json_values_new(root); fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.access_token"); - fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid"); + + /* extremely silly difference */ + if (priv->is_work) { + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.uid"); + } else { + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid"); + } + fb_json_values_update(values, &err); FB_API_ERROR_EMIT(api, err, @@ -2185,8 +2272,9 @@ fb_api_cb_auth(PurpleHttpConnection *con, PurpleHttpResponse *res, } void -fb_api_auth(FbApi *api, const gchar *user, const gchar *pass) +fb_api_auth(FbApi *api, const gchar *user, const gchar *pass, const gchar *credentials_type) { + FbApiPrivate *priv = api->priv; FbHttpParams *prms; prms = fb_http_params_new(); @@ -2206,6 +2294,7 @@ fb_api_user_icon_checksum(gchar *icon) return NULL; } + prms = fb_http_params_new(); prms = fb_http_params_new_parse(icon, TRUE); csum = fb_http_params_dup_str(prms, "oh", NULL); fb_http_params_free(prms); @@ -2219,8 +2308,7 @@ fb_api_user_icon_checksum(gchar *icon) } static void -fb_api_cb_contact(PurpleHttpConnection *con, PurpleHttpResponse *res, - gpointer data) +fb_api_cb_contact(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { const gchar *str; FbApi *api = data; @@ -2300,6 +2388,8 @@ fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users) "$.represented_profile.id"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.represented_profile.friendship_status"); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, + "$.is_on_viewer_contact_list"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.structured_name.text"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, @@ -2312,9 +2402,12 @@ fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users) } while (fb_json_values_update(values, &err)) { + gboolean in_contact_list; + str = fb_json_values_next_str(values, "0"); uid = FB_ID_FROM_STR(str); str = fb_json_values_next_str(values, NULL); + in_contact_list = fb_json_values_next_bool(values, FALSE); if ((!purple_strequal(str, "ARE_FRIENDS") && (uid != priv->uid)) || (uid == 0)) @@ -2369,8 +2462,7 @@ fb_api_cb_contacts_parse_removed(FbApi *api, JsonNode *node, GSList *users) } static void -fb_api_cb_contacts(PurpleHttpConnection *con, PurpleHttpResponse *res, - gpointer data) +fb_api_cb_contacts(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { const gchar *cursor; const gchar *delta_cursor; @@ -2578,6 +2670,15 @@ fb_api_message_send(FbApi *api, FbApiMessage *msg) g_free(json); } +static gboolean +fb_api_is_message_not_empty(const gchar *text) +{ + while (*text && *text == ' ') { + text++; + } + return *text != '\0'; +} + void fb_api_message(FbApi *api, FbId id, gboolean thread, const gchar *text) { @@ -2587,6 +2688,7 @@ fb_api_message(FbApi *api, FbId id, gboolean thread, const gchar *text) g_return_if_fail(FB_IS_API(api)); g_return_if_fail(text != NULL); + g_return_if_fail(fb_api_is_message_not_empty(text)); priv = api->priv; msg = fb_api_message_dup(NULL, FALSE); @@ -2635,7 +2737,7 @@ fb_api_publish(FbApi *api, const gchar *topic, const gchar *format, ...) fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, "Writing message (topic: %s)", - topic); + topic); fb_mqtt_publish(priv->mqtt, topic, cytes); g_byte_array_free(cytes, TRUE); @@ -2677,8 +2779,7 @@ fb_api_cb_unread_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg, GError *err = NULL; values = fb_json_values_new(root); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, - "$.attachment_fbid"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.attachment_fbid"); fb_json_values_set_array(values, FALSE, "$.blob_attachments"); while (fb_json_values_update(values, &err)) { @@ -2697,8 +2798,7 @@ fb_api_cb_unread_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg, } static void -fb_api_cb_unread_msgs(PurpleHttpConnection *con, PurpleHttpResponse *res, - gpointer data) +fb_api_cb_unread_msgs(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { const gchar *body; const gchar *str; @@ -2748,8 +2848,7 @@ fb_api_cb_unread_msgs(PurpleHttpConnection *con, PurpleHttpResponse *res, fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.message_sender.messaging_actor.id"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.message.text"); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, - "$.timestamp_precise"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.timestamp_precise"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.sticker.id"); fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.message_id"); fb_json_values_set_array(values, FALSE, "$.messages.nodes"); @@ -2808,8 +2907,7 @@ fb_api_cb_unread_msgs(PurpleHttpConnection *con, PurpleHttpResponse *res, continue; } - msgs = fb_api_cb_unread_parse_attach(api, str, &msg, msgs, - node, &err); + msgs = fb_api_cb_unread_parse_attach(api, str, &msg, msgs, node, &err); if (G_UNLIKELY(err != NULL)) { break; @@ -2850,8 +2948,7 @@ fb_api_cb_unread(PurpleHttpConnection *con, PurpleHttpResponse *res, "$.thread_key.other_user_id"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.thread_key.thread_fbid"); - fb_json_values_set_array(values, FALSE, "$.viewer.message_threads" - ".nodes"); + fb_json_values_set_array(values, FALSE, "$.viewer.message_threads.nodes"); while (fb_json_values_update(values, &err)) { count = fb_json_values_next_int(values, -5); @@ -2910,8 +3007,7 @@ fb_api_unread(FbApi *api) } static void -fb_api_cb_sticker(PurpleHttpConnection *con, PurpleHttpResponse *res, - gpointer data) +fb_api_cb_sticker(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { FbApi *api = data; FbApiMessage *msg; @@ -2927,8 +3023,7 @@ fb_api_cb_sticker(PurpleHttpConnection *con, PurpleHttpResponse *res, node = fb_json_node_get_nth(root, 0); values = fb_json_values_new(node); - fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, - "$.thread_image.uri"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.thread_image.uri"); fb_json_values_update(values, &err); FB_API_ERROR_EMIT(api, err, @@ -2960,7 +3055,7 @@ fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg) fb_json_bldr_arr_end(bldr); http = fb_api_http_query(api, FB_API_QUERY_STICKER, bldr, - fb_api_cb_sticker); + fb_api_cb_sticker); fb_api_data_set(api, http, msg, (GDestroyNotify) fb_api_message_free); } @@ -3039,8 +3134,7 @@ fb_api_thread_parse(FbApi *api, FbApiThread *thrd, JsonNode *root, } static void -fb_api_cb_thread(PurpleHttpConnection *con, PurpleHttpResponse *res, - gpointer data) +fb_api_cb_thread(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { FbApi *api = data; FbApiThread thrd; @@ -3099,8 +3193,7 @@ fb_api_thread(FbApi *api, FbId tid) } static void -fb_api_cb_thread_create(PurpleHttpConnection *con, PurpleHttpResponse *res, - gpointer data) +fb_api_cb_thread_create(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { const gchar *str; FbApi *api = data; @@ -3431,9 +3524,7 @@ fb_api_thread_dup(const FbApiThread *thrd, gboolean deep) ret = g_memdup2(thrd, sizeof *thrd); if (deep) { - ret->users = NULL; - - for (l = thrd->users; l != NULL; l = l->next) { + for (ret->users = NULL, l = thrd->users; l != NULL; l = l->next) { user = fb_api_user_dup(l->data, TRUE); ret->users = g_slist_prepend(ret->users, user); } diff --git a/pidgin/libpurple/protocols/facebook/api.h b/pidgin/libpurple/protocols/facebook/api.h index 63c9a1b9..d70eda5a 100644 --- a/pidgin/libpurple/protocols/facebook/api.h +++ b/pidgin/libpurple/protocols/facebook/api.h @@ -97,21 +97,44 @@ */ #define FB_API_SECRET "374e60f8b9bb6b8cbb30f78030438895" +/** + * FB_WORK_API_KEY: + * + * The Facebook workchat app API key. + */ +#define FB_WORK_API_KEY "312713275593566" + +/** + * FB_WORK_API_SECRET: + * + * The Facebook workchat app API secret. + */ +#define FB_WORK_API_SECRET "d2901dc6cb685df3b074b30b56b78d28" + /** * FB_ORCA_AGENT * * The part of the user agent that looks like the official client, since the * server started checking this. + * + * We announce ourselves as compatible with Orca-Android 38.0 since that's the + * closest version to the last major protocol update. Some parts use older + * features, some parts use newer ones. + * + * Fun fact: this version sends old-style MQIsdp CONNECT messages for the first + * connection, with JSON payloads instead of compressed thrift. + * */ -#define FB_ORCA_AGENT "[FBAN/Orca-Android;FBAV/537.0.0.31.101;FBPN/com.facebook.orca;FBLC/en_US;FBBV/52182662]" +#define FB_ORCA_AGENT "[FBAN/Orca-Android;FBAV/537.0.0.31.101;FBBV/14477681]" /** * FB_API_AGENT: * * The HTTP User-Agent header. */ -#define FB_API_AGENT "Facebook plugin / Purple / " PACKAGE_VERSION " " FB_ORCA_AGENT +#define FB_API_AGENT_BASE "Facebook plugin / BitlBee / " PACKAGE_VERSION +#define FB_API_AGENT FB_API_AGENT_BASE " " FB_ORCA_AGENT /** * FB_API_MQTT_AGENT @@ -136,6 +159,15 @@ */ #define FB_API_URL_AUTH FB_API_BHOST "/method/auth.login" +/** + * FB_API_URL_WORK_PRELOGIN + * + * The URL for workchat pre-login information, indicating what auth method + * should be used + */ + +#define FB_API_URL_WORK_PRELOGIN FB_API_GHOST "/at_work/pre_login_info" + /** * FB_API_URL_GQL: * @@ -171,6 +203,14 @@ */ #define FB_API_URL_TOPIC FB_API_AHOST "/method/messaging.setthreadname" +/** + * FB_API_SSO_URL: + * + * Template for the URL shown to workchat users when trying to authenticate + * with SSO. + */ +#define FB_API_SSO_URL "https://m.facebook.com/work/sso/mobile?app_id=312713275593566&response_url=fb-workchat-sso%%3A%%2F%%2Fsso&request_id=%s&code_challenge=%s&email=%s" + /** * FB_API_QUERY_CONTACT: * @@ -318,6 +358,16 @@ */ #define FB_API_QUERY_XMA 10153919431161729 +/** + * FB_API_WORK_COMMUNITY_PEEK: + * + * The docid with information about the work community of the currently + * authenticated user. + * + * Used when prelogin returns can_login_via_linked_account + */ +#define FB_API_WORK_COMMUNITY_PEEK 1295334753880530 + /** * FB_API_CONTACTS_COUNT: * @@ -341,15 +391,15 @@ * pointer of a pointer to a #GError. */ #define FB_API_TCHK(e) \ - G_STMT_START { \ - if (G_UNLIKELY(!(e))) { \ - g_set_error(error, FB_API_ERROR, FB_API_ERROR_GENERAL, \ - "Failed to read thrift: %s:%d " \ - "%s: assertion '%s' failed", \ - __FILE__, __LINE__, G_STRFUNC, #e); \ - return; \ - } \ - } G_STMT_END + G_STMT_START { \ + if (G_UNLIKELY(!(e))) { \ + g_set_error(error, FB_API_ERROR, FB_API_ERROR_GENERAL, \ + "Failed to read thrift: %s:%d " \ + "%s: assertion '%s' failed", \ + __FILE__, __LINE__, G_STRFUNC, #e); \ + return; \ + } \ + } G_STMT_END /** * FB_API_MSGID: @@ -361,9 +411,9 @@ * Returns: The message identifier. */ #define FB_API_MSGID(m, i) ((guint64) ( \ - (((guint32) i) & 0x3FFFFF) | \ - (((guint64) m) << 22) \ - )) + (((guint32) i) & 0x3FFFFF) | \ + (((guint64) m) << 22) \ + )) /** * FB_API_ERROR_EMIT: @@ -374,12 +424,12 @@ * Emits a #GError on behalf of the #FbApi. */ #define FB_API_ERROR_EMIT(a, e, c) \ - G_STMT_START { \ - if (G_UNLIKELY((e) != NULL)) { \ - fb_api_error_emit(a, e); \ - {c;} \ - } \ - } G_STMT_END + G_STMT_START { \ + if (G_UNLIKELY((e) != NULL)) { \ + fb_api_error_emit(a, e); \ + {c;} \ + } \ + } G_STMT_END /** * FB_API_ERROR: @@ -409,10 +459,10 @@ typedef struct _FbApiUser FbApiUser; */ typedef enum { - FB_API_ERROR_GENERAL, - FB_API_ERROR_AUTH, - FB_API_ERROR_QUEUE, - FB_API_ERROR_NONFATAL + FB_API_ERROR_GENERAL, + FB_API_ERROR_AUTH, + FB_API_ERROR_QUEUE, + FB_API_ERROR_NONFATAL } FbApiError; /** @@ -425,9 +475,9 @@ typedef enum */ typedef enum { - FB_API_EVENT_TYPE_THREAD_TOPIC, - FB_API_EVENT_TYPE_THREAD_USER_ADDED, - FB_API_EVENT_TYPE_THREAD_USER_REMOVED + FB_API_EVENT_TYPE_THREAD_TOPIC, + FB_API_EVENT_TYPE_THREAD_USER_ADDED, + FB_API_EVENT_TYPE_THREAD_USER_REMOVED } FbApiEventType; /** @@ -440,11 +490,44 @@ typedef enum */ typedef enum { - FB_API_MESSAGE_FLAG_DONE = 1 << 0, - FB_API_MESSAGE_FLAG_IMAGE = 1 << 1, - FB_API_MESSAGE_FLAG_SELF = 1 << 2 + FB_API_MESSAGE_FLAG_DONE = 1 << 0, + FB_API_MESSAGE_FLAG_IMAGE = 1 << 1, + FB_API_MESSAGE_FLAG_SELF = 1 << 2 } FbApiMessageFlags; +/** +* FbApiClientCapabilities: +* @FB_CP_ACKNOWLEDGED_DELIVERY: +* @FB_CP_PROCESSING_LASTACTIVE_PRESENCEINFO: +* @FB_CP_EXACT_KEEPALIVE: +* @FB_CP_REQUIRES_JSON_UNICODE_ESCAPES: +* @FB_CP_DELTA_SENT_MESSAGE_ENABLED: +* @FB_CP_USE_ENUM_TOPIC: All topics are numeric. +* @FB_CP_SUPPRESS_GETDIFF_IN_CONNECT: +* @FB_CP_USE_THRIFT_FOR_INBOX: +* @FB_CP_USE_SEND_PINGRESP: +* @FB_CP_REQUIRE_REPLAY_PROTECTION: +* @FB_CP_DATA_SAVING_MODE: +* @FB_CP_TYPING_OFF_WHEN_SENDING_MESSAGE: +* +* The client capabilities. +*/ +typedef enum +{ + FB_CP_ACKNOWLEDGED_DELIVERY = 1 << 0, + FB_CP_PROCESSING_LASTACTIVE_PRESENCEINFO = 1 << 1, + FB_CP_EXACT_KEEPALIVE = 1 << 2, + FB_CP_REQUIRES_JSON_UNICODE_ESCAPES = 1 << 3, + FB_CP_DELTA_SENT_MESSAGE_ENABLED = 1 << 4, + FB_CP_USE_ENUM_TOPIC = 1 << 5, + FB_CP_SUPPRESS_GETDIFF_IN_CONNECT = 1 << 6, + FB_CP_USE_THRIFT_FOR_INBOX = 1 << 7, + FB_CP_USE_SEND_PINGRESP = 1 << 8, + FB_CP_REQUIRE_REPLAY_PROTECTION = 1 << 9, + FB_CP_DATA_SAVING_MODE = 1 << 10, + FB_CP_TYPING_OFF_WHEN_SENDING_MESSAGE = 1 << 11 +} FbApiClientCapabilities; + /** * FbApi: * @@ -452,9 +535,9 @@ typedef enum */ struct _FbApi { - /*< private >*/ - GObject parent; - FbApiPrivate *priv; + /*< private >*/ + GObject parent; + FbApiPrivate *priv; }; /** @@ -464,8 +547,8 @@ struct _FbApi */ struct _FbApiClass { - /*< private >*/ - GObjectClass parent_class; + /*< private >*/ + GObjectClass parent_class; }; /** @@ -479,10 +562,10 @@ struct _FbApiClass */ struct _FbApiEvent { - FbApiEventType type; - FbId uid; - FbId tid; - gchar *text; + FbApiEventType type; + FbId uid; + FbId tid; + gchar *text; }; /** @@ -497,11 +580,11 @@ struct _FbApiEvent */ struct _FbApiMessage { - FbApiMessageFlags flags; - FbId uid; - FbId tid; - gint64 tstamp; - gchar *text; + FbApiMessageFlags flags; + FbId uid; + FbId tid; + gint64 tstamp; + gchar *text; }; /** @@ -513,8 +596,8 @@ struct _FbApiMessage */ struct _FbApiPresence { - FbId uid; - gboolean active; + FbId uid; + gboolean active; }; /** @@ -527,9 +610,9 @@ struct _FbApiPresence */ struct _FbApiThread { - FbId tid; - gchar *topic; - GSList *users; + FbId tid; + gchar *topic; + GSList *users; }; /** @@ -541,8 +624,8 @@ struct _FbApiThread */ struct _FbApiTyping { - FbId uid; - gboolean state; + FbId uid; + gboolean state; }; /** @@ -556,10 +639,10 @@ struct _FbApiTyping */ struct _FbApiUser { - FbId uid; - gchar *name; - gchar *icon; - gchar *csum; + FbId uid; + gchar *name; + gchar *icon; + gchar *csum; }; /** @@ -641,12 +724,49 @@ fb_api_error_emit(FbApi *api, GError *error); * @api: The #FbApi. * @user: The Facebook user name, email, or phone number. * @pass: The Facebook password. + * @credentials_type: Type of work account credentials, or NULL * * Sends an authentication request to Facebook. This will obtain * session information, which is required for all other requests. */ void -fb_api_auth(FbApi *api, const gchar *user, const gchar *pass); +fb_api_auth(FbApi *api, const gchar *user, const gchar *pass, const gchar *credentials_type); + +/** + * fb_api_work_login: + * @api: The #FbApi. + * @user: The Facebook user name, email, or phone number. + * @pass: The Facebook password. + * + * Starts the workchat login sequence. + */ +void +fb_api_work_login(FbApi *api, gchar *user, gchar *pass); + +/** + * fb_api_work_gen_sso_url: + * @api: The #FbApi. + * @user: The Facebook user email. + * + * Generates the URL to be shown to the user to get the SSO auth token. This + * url contains a challenge and the corresponding verifier is saved in the + * FbApi instance to be used later. + * + * Returns: a newly allocated string. + */ +gchar * +fb_api_work_gen_sso_url(FbApi *api, const gchar *user); + +/** + * fb_api_work_got_nonce: + * @api: The #FbApi. + * @url: The fb-workchat-sso:// URL as entered by the user + * + * Parses the fb-workchat-sso:// URL that the user got redirected to and + * continues with work_sso_nonce auth + */ +void +fb_api_work_got_nonce(FbApi *api, const gchar *url); /** * fb_api_contact: diff --git a/pidgin/libpurple/protocols/facebook/facebook.c b/pidgin/libpurple/protocols/facebook/facebook.c index 01f3bf81..fc99c2b4 100644 --- a/pidgin/libpurple/protocols/facebook/facebook.c +++ b/pidgin/libpurple/protocols/facebook/facebook.c @@ -65,1533 +65,1533 @@ fb_cb_api_messages(FbApi *api, GSList *msgs, gpointer data); static PurpleGroup * fb_get_group(gboolean friend) { - PurpleBlistNode *n; - PurpleBlistNode *node; - PurpleGroup *grp; - const gchar *title; + PurpleBlistNode *n; + PurpleBlistNode *node; + PurpleGroup *grp; + const gchar *title; - if (friend) { - title = _("Facebook Friends"); - } else { - title = _("Facebook Non-Friends"); - } + if (friend) { + title = _("Facebook Friends"); + } else { + title = _("Facebook Non-Friends"); + } - grp = purple_blist_find_group(title); + grp = purple_blist_find_group(title); - if (G_UNLIKELY(grp == NULL)) { - grp = purple_group_new(title); - node = NULL; + if (G_UNLIKELY(grp == NULL)) { + grp = purple_group_new(title); + node = NULL; - for (n = purple_blist_get_root(); n != NULL; n = n->next) { - node = n; - } + for (n = purple_blist_get_root(); n != NULL; n = n->next) { + node = n; + } - /* Append to the end of the buddy list */ - purple_blist_add_group(grp, node); + /* Append to the end of the buddy list */ + purple_blist_add_group(grp, node); - if (!friend) { - node = PURPLE_BLIST_NODE(grp); - purple_blist_node_set_bool(node, "collapsed", TRUE); - } - } + if (!friend) { + node = PURPLE_BLIST_NODE(grp); + purple_blist_node_set_bool(node, "collapsed", TRUE); + } + } - return grp; + return grp; } static void fb_buddy_add_nonfriend(PurpleAccount *acct, FbApiUser *user) { - gchar uid[FB_ID_STRMAX]; - PurpleBuddy *bdy; - PurpleGroup *grp; + gchar uid[FB_ID_STRMAX]; + PurpleBuddy *bdy; + PurpleGroup *grp; - FB_ID_TO_STR(user->uid, uid); - bdy = purple_buddy_new(acct, uid, user->name); - grp = fb_get_group(FALSE); + FB_ID_TO_STR(user->uid, uid); + bdy = purple_buddy_new(acct, uid, user->name); + grp = fb_get_group(FALSE); - purple_buddy_set_server_alias(bdy, user->name); - purple_blist_add_buddy(bdy, NULL, grp, NULL); + purple_buddy_set_server_alias(bdy, user->name); + purple_blist_add_buddy(bdy, NULL, grp, NULL); } static void fb_cb_api_auth(FbApi *api, gpointer data) { - FbData *fata = data; - PurpleConnection *gc; + FbData *fata = data; + PurpleConnection *gc; - gc = fb_data_get_connection(fata); + gc = fb_data_get_connection(fata); - purple_connection_update_progress(gc, _("Fetching contacts"), 2, 4); - fb_data_save(fata); - fb_api_contacts(api); + purple_connection_update_progress(gc, _("Fetching contacts"), 2, 4); + fb_data_save(fata); + fb_api_contacts(api); } static void fb_cb_api_connect(FbApi *api, gpointer data) { - FbData *fata = data; - PurpleAccount *acct; - PurpleConnection *gc; + FbData *fata = data; + PurpleAccount *acct; + PurpleConnection *gc; - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); - fb_data_save(fata); - purple_connection_set_state(gc, PURPLE_CONNECTION_CONNECTED); + fb_data_save(fata); + purple_connection_set_state(gc, PURPLE_CONNECTION_CONNECTED); - if (purple_account_get_bool(acct, "show-unread", TRUE)) { - fb_api_unread(api); - } + if (purple_account_get_bool(acct, "show-unread", TRUE)) { + fb_api_unread(api); + } } static void fb_cb_api_contact(FbApi *api, FbApiUser *user, gpointer data) { - FbData *fata = data; - gchar uid[FB_ID_STRMAX]; - GSList *msgs; - PurpleAccount *acct; - PurpleConnection *gc; - - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); - FB_ID_TO_STR(user->uid, uid); - - if (purple_blist_find_buddy(acct, uid) == NULL) { - fb_buddy_add_nonfriend(acct, user); - } - - msgs = fb_data_take_messages(fata, user->uid); - - if (msgs != NULL) { - fb_cb_api_messages(api, msgs, fata); - g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); - } + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + GSList *msgs; + PurpleAccount *acct; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + FB_ID_TO_STR(user->uid, uid); + + if (purple_blist_find_buddy(acct, uid) == NULL) { + fb_buddy_add_nonfriend(acct, user); + } + + msgs = fb_data_take_messages(fata, user->uid); + + if (msgs != NULL) { + fb_cb_api_messages(api, msgs, fata); + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + } } static gboolean fb_cb_sync_contacts(gpointer data) { - FbApi *api; - FbData *fata = data; + FbApi *api; + FbData *fata = data; - api = fb_data_get_api(fata); - fb_data_clear_timeout(fata, "sync-contacts", FALSE); - fb_api_contacts(api); - return FALSE; + api = fb_data_get_api(fata); + fb_data_clear_timeout(fata, "sync-contacts", FALSE); + fb_api_contacts(api); + return FALSE; } static void fb_cb_icon(FbDataImage *img, GError *error) { - const gchar *csum; - const gchar *name; - const gchar *str; - FbHttpParams *params; - gsize size; - guint8 *image; - PurpleAccount *acct; - PurpleBuddy *bdy; - - bdy = fb_data_image_get_data(img); - acct = purple_buddy_get_account(bdy); - name = purple_buddy_get_name(bdy); - - if (G_UNLIKELY(error != NULL)) { - fb_util_debug_warning("Failed to retrieve icon for %s: %s", - name, error->message); - return; - } - - str = fb_data_image_get_url(img); - params = fb_http_params_new_parse(str, TRUE); - csum = fb_http_params_get_str(params, "oh", NULL); - - image = fb_data_image_dup_image(img, &size); - purple_buddy_icons_set_for_user(acct, name, image, size, csum); - fb_http_params_free(params); + const gchar *csum; + const gchar *name; + const gchar *str; + FbHttpParams *params; + gsize size; + guint8 *image; + PurpleAccount *acct; + PurpleBuddy *bdy; + + bdy = fb_data_image_get_data(img); + acct = purple_buddy_get_account(bdy); + name = purple_buddy_get_name(bdy); + + if (G_UNLIKELY(error != NULL)) { + fb_util_debug_warning("Failed to retrieve icon for %s: %s", + name, error->message); + return; + } + + str = fb_data_image_get_url(img); + params = fb_http_params_new_parse(str, TRUE); + csum = fb_http_params_get_str(params, "oh", NULL); + + image = fb_data_image_dup_image(img, &size); + purple_buddy_icons_set_for_user(acct, name, image, size, csum); + fb_http_params_free(params); } static void fb_sync_contacts_add_timeout(FbData *fata) { - gint sync; - PurpleConnection *gc; - PurpleAccount *acct; + gint sync; + PurpleConnection *gc; + PurpleAccount *acct; - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); - sync = purple_account_get_int(acct, "sync-interval", 5); + sync = purple_account_get_int(acct, "sync-interval", 5); - if (sync < 1) { - purple_account_set_int(acct, "sync-interval", 1); - sync = 1; - } + if (sync < 1) { + purple_account_set_int(acct, "sync-interval", 1); + sync = 1; + } - sync *= 60 * 1000; - fb_data_add_timeout(fata, "sync-contacts", sync, fb_cb_sync_contacts, - fata); + sync *= 60 * 1000; + fb_data_add_timeout(fata, "sync-contacts", sync, fb_cb_sync_contacts, + fata); } static void fb_cb_api_contacts(FbApi *api, GSList *users, gboolean complete, gpointer data) { - const gchar *alias; - const gchar *csum; - FbApiUser *user; - FbData *fata = data; - FbId muid; - gchar uid[FB_ID_STRMAX]; - GSList *l; - GValue val = G_VALUE_INIT; - PurpleAccount *acct; - PurpleBuddy *bdy; - PurpleConnection *gc; - PurpleConnectionState state; - PurpleGroup *grp; - PurpleGroup *grpn; - PurpleStatus *status; - PurpleStatusPrimitive pstat; - PurpleStatusType *type; - - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); - grp = fb_get_group(TRUE); - grpn = fb_get_group(FALSE); - alias = purple_account_get_private_alias(acct); - state = purple_connection_get_state(gc); - - g_value_init(&val, FB_TYPE_ID); - g_object_get_property(G_OBJECT(api), "uid", &val); - muid = g_value_get_int64(&val); - g_value_unset(&val); - - for (l = users; l != NULL; l = l->next) { - user = l->data; - FB_ID_TO_STR(user->uid, uid); - - if (G_UNLIKELY(user->uid == muid)) { - if (G_UNLIKELY(alias != NULL)) { - continue; - } - - purple_account_set_private_alias(acct, user->name); - continue; - } - - bdy = purple_blist_find_buddy(acct, uid); - - if ((bdy != NULL) && (purple_buddy_get_group(bdy) == grpn)) { - purple_blist_remove_buddy(bdy); - bdy = NULL; - } - - if (bdy == NULL) { - bdy = purple_buddy_new(acct, uid, NULL); - purple_blist_add_buddy(bdy, NULL, grp, NULL); - } - - purple_buddy_set_server_alias(bdy, user->name); - csum = purple_buddy_icons_get_checksum_for_user(bdy); - - if (!purple_strequal(csum, user->csum)) { - fb_data_image_add(fata, user->icon, fb_cb_icon, - bdy, NULL); - } - } - - fb_data_image_queue(fata); - - if (!complete) { - return; - } - - if (state != PURPLE_CONNECTION_CONNECTED) { - status = purple_account_get_active_status(acct); - type = purple_status_get_status_type(status); - pstat = purple_status_type_get_primitive(type); - - purple_connection_update_progress(gc, _("Connecting"), 3, 4); - fb_api_connect(api, pstat == PURPLE_STATUS_INVISIBLE); - } - - fb_sync_contacts_add_timeout(fata); + const gchar *alias; + const gchar *csum; + FbApiUser *user; + FbData *fata = data; + FbId muid; + gchar uid[FB_ID_STRMAX]; + GSList *l; + GValue val = G_VALUE_INIT; + PurpleAccount *acct; + PurpleBuddy *bdy; + PurpleConnection *gc; + PurpleConnectionState state; + PurpleGroup *grp; + PurpleGroup *grpn; + PurpleStatus *status; + PurpleStatusPrimitive pstat; + PurpleStatusType *type; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + grp = fb_get_group(TRUE); + grpn = fb_get_group(FALSE); + alias = purple_account_get_private_alias(acct); + state = purple_connection_get_state(gc); + + g_value_init(&val, FB_TYPE_ID); + g_object_get_property(G_OBJECT(api), "uid", &val); + muid = g_value_get_int64(&val); + g_value_unset(&val); + + for (l = users; l != NULL; l = l->next) { + user = l->data; + FB_ID_TO_STR(user->uid, uid); + + if (G_UNLIKELY(user->uid == muid)) { + if (G_UNLIKELY(alias != NULL)) { + continue; + } + + purple_account_set_private_alias(acct, user->name); + continue; + } + + bdy = purple_blist_find_buddy(acct, uid); + + if ((bdy != NULL) && (purple_buddy_get_group(bdy) == grpn)) { + purple_blist_remove_buddy(bdy); + bdy = NULL; + } + + if (bdy == NULL) { + bdy = purple_buddy_new(acct, uid, NULL); + purple_blist_add_buddy(bdy, NULL, grp, NULL); + } + + purple_buddy_set_server_alias(bdy, user->name); + csum = purple_buddy_icons_get_checksum_for_user(bdy); + + if (!purple_strequal(csum, user->csum)) { + fb_data_image_add(fata, user->icon, fb_cb_icon, + bdy, NULL); + } + } + + fb_data_image_queue(fata); + + if (!complete) { + return; + } + + if (state != PURPLE_CONNECTION_CONNECTED) { + status = purple_account_get_active_status(acct); + type = purple_status_get_status_type(status); + pstat = purple_status_type_get_primitive(type); + + purple_connection_update_progress(gc, _("Connecting"), 3, 4); + fb_api_connect(api, pstat == PURPLE_STATUS_INVISIBLE); + } + + fb_sync_contacts_add_timeout(fata); } static void fb_cb_api_contacts_delta(FbApi *api, GSList *added, GSList *removed, gpointer data) { - FbApiUser *user; - FbData *fata = data; - gchar uid[FB_ID_STRMAX]; - GSList *l; - PurpleAccount *acct; - PurpleBuddy *bdy; - PurpleConnection *gc; - PurpleGroup *grp; - PurpleGroup *grpn; - - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); - grp = fb_get_group(TRUE); - grpn = fb_get_group(FALSE); - - for (l = added; l != NULL; l = l->next) { - user = l->data; - FB_ID_TO_STR(user->uid, uid); - - bdy = purple_blist_find_buddy(acct, uid); - - if ((bdy != NULL) && (purple_buddy_get_group(bdy) == grpn)) { - purple_blist_remove_buddy(bdy); - } - - bdy = purple_buddy_new(acct, uid, NULL); - purple_blist_add_buddy(bdy, NULL, grp, NULL); - - purple_buddy_set_server_alias(bdy, user->name); - } - - for (l = removed; l != NULL; l = l->next) { - bdy = purple_blist_find_buddy(acct, l->data); - - if (bdy != NULL) { - purple_blist_remove_buddy(bdy); - } - } - - fb_sync_contacts_add_timeout(fata); + FbApiUser *user; + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + GSList *l; + PurpleAccount *acct; + PurpleBuddy *bdy; + PurpleConnection *gc; + PurpleGroup *grp; + PurpleGroup *grpn; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + grp = fb_get_group(TRUE); + grpn = fb_get_group(FALSE); + + for (l = added; l != NULL; l = l->next) { + user = l->data; + FB_ID_TO_STR(user->uid, uid); + + bdy = purple_blist_find_buddy(acct, uid); + + if ((bdy != NULL) && (purple_buddy_get_group(bdy) == grpn)) { + purple_blist_remove_buddy(bdy); + } + + bdy = purple_buddy_new(acct, uid, NULL); + purple_blist_add_buddy(bdy, NULL, grp, NULL); + + purple_buddy_set_server_alias(bdy, user->name); + } + + for (l = removed; l != NULL; l = l->next) { + bdy = purple_blist_find_buddy(acct, l->data); + + if (bdy != NULL) { + purple_blist_remove_buddy(bdy); + } + } + + fb_sync_contacts_add_timeout(fata); } static void fb_cb_api_error(FbApi *api, GError *error, gpointer data) { - FbData *fata = data; - PurpleConnection *gc; - PurpleConnectionError errc; - - gc = fb_data_get_connection(fata); - - if (error->domain == FB_MQTT_SSL_ERROR) { - purple_connection_ssl_error(gc, error->code); - return; - } - - if (g_error_matches(error, FB_API_ERROR, FB_API_ERROR_QUEUE)) { - /* Save the reset data */ - fb_data_save(fata); - } - - if ((error->domain == FB_HTTP_ERROR) && - (error->code >= 400) && - (error->code <= 500)) - { - errc = PURPLE_CONNECTION_ERROR_OTHER_ERROR; - } else if (g_error_matches(error, FB_API_ERROR, FB_API_ERROR_AUTH)) { - errc = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; - } else { - errc = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; - } - - - if (!g_error_matches(error, FB_API_ERROR, FB_API_ERROR_NONFATAL)) { - purple_connection_error(gc, errc, error->message); - } + FbData *fata = data; + PurpleConnection *gc; + PurpleConnectionError errc; + + gc = fb_data_get_connection(fata); + + if (error->domain == FB_MQTT_SSL_ERROR) { + purple_connection_ssl_error(gc, error->code); + return; + } + + if (g_error_matches(error, FB_API_ERROR, FB_API_ERROR_QUEUE)) { + /* Save the reset data */ + fb_data_save(fata); + } + + if ((error->domain == FB_HTTP_ERROR) && + (error->code >= 400) && + (error->code <= 500)) + { + errc = PURPLE_CONNECTION_ERROR_OTHER_ERROR; + } else if (g_error_matches(error, FB_API_ERROR, FB_API_ERROR_AUTH)) { + errc = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + } else { + errc = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + } + + + if (!g_error_matches(error, FB_API_ERROR, FB_API_ERROR_NONFATAL)) { + purple_connection_error(gc, errc, error->message); + } } static void fb_cb_api_events(FbApi *api, GSList *events, gpointer data) { - FbData *fata = data; - FbApiEvent *event; - gchar uid[FB_ID_STRMAX]; - gchar tid[FB_ID_STRMAX]; - GHashTable *fetch; - GHashTableIter iter; - GSList *l; - PurpleAccount *acct; - PurpleChatConversation *chat; - PurpleConnection *gc; - - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); - fetch = g_hash_table_new(fb_id_hash, fb_id_equal); - - for (l = events; l != NULL; l = l->next) { - event = l->data; - - FB_ID_TO_STR(event->tid, tid); - chat = purple_conversations_find_chat_with_account(tid, acct); - - if (chat == NULL) { - continue; - } - - FB_ID_TO_STR(event->uid, uid); - - switch (event->type) { - case FB_API_EVENT_TYPE_THREAD_TOPIC: - purple_chat_conversation_set_topic(chat, uid, - event->text); - break; - - case FB_API_EVENT_TYPE_THREAD_USER_ADDED: - if (purple_blist_find_buddy(acct, uid) == NULL) { - if (event->text) { - FbApiUser *user = fb_api_user_dup(NULL, FALSE); - user->uid = event->uid; - user->name = g_strdup(event->text); - - fb_buddy_add_nonfriend(acct, user); - - fb_api_user_free(user); - } else { - g_hash_table_insert(fetch, &event->tid, event); - break; - } - } - - purple_chat_conversation_add_user(chat, uid, NULL, 0, - TRUE); - break; - - case FB_API_EVENT_TYPE_THREAD_USER_REMOVED: - purple_chat_conversation_remove_user(chat, uid, event->text); - break; - } - } - - g_hash_table_iter_init(&iter, fetch); - - while (g_hash_table_iter_next(&iter, NULL, (gpointer) &event)) { - fb_api_thread(api, event->tid); - } - - g_hash_table_destroy(fetch); + FbData *fata = data; + FbApiEvent *event; + gchar uid[FB_ID_STRMAX]; + gchar tid[FB_ID_STRMAX]; + GHashTable *fetch; + GHashTableIter iter; + GSList *l; + PurpleAccount *acct; + PurpleChatConversation *chat; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + fetch = g_hash_table_new(fb_id_hash, fb_id_equal); + + for (l = events; l != NULL; l = l->next) { + event = l->data; + + FB_ID_TO_STR(event->tid, tid); + chat = purple_conversations_find_chat_with_account(tid, acct); + + if (chat == NULL) { + continue; + } + + FB_ID_TO_STR(event->uid, uid); + + switch (event->type) { + case FB_API_EVENT_TYPE_THREAD_TOPIC: + purple_chat_conversation_set_topic(chat, uid, + event->text); + break; + + case FB_API_EVENT_TYPE_THREAD_USER_ADDED: + if (purple_blist_find_buddy(acct, uid) == NULL) { + if (event->text) { + FbApiUser *user = fb_api_user_dup(NULL, FALSE); + user->uid = event->uid; + user->name = g_strdup(event->text); + + fb_buddy_add_nonfriend(acct, user); + + fb_api_user_free(user); + } else { + g_hash_table_insert(fetch, &event->tid, event); + break; + } + } + + purple_chat_conversation_add_user(chat, uid, NULL, 0, + TRUE); + break; + + case FB_API_EVENT_TYPE_THREAD_USER_REMOVED: + purple_chat_conversation_remove_user(chat, uid, event->text); + break; + } + } + + g_hash_table_iter_init(&iter, fetch); + + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &event)) { + fb_api_thread(api, event->tid); + } + + g_hash_table_destroy(fetch); } static void fb_cb_image(FbDataImage *img, GError *error) { - const gchar *url; - FbApi *api; - FbApiMessage *msg; - FbData *fata; - gsize size; - GSList *msgs = NULL; - guint id; - guint8 *image; - PurpleImage *pimg; - - fata = fb_data_image_get_fata(img); - msg = fb_data_image_get_data(img); - - if (G_UNLIKELY(error != NULL)) { - url = fb_data_image_get_url(img); - fb_util_debug_warning("Failed to retrieve image %s: %s", - url, error->message); - return; - } - - api = fb_data_get_api(fata); - image = fb_data_image_dup_image(img, &size); - pimg = purple_image_new_from_data(image, size); - id = purple_image_store_add_weak(pimg); - - g_free(msg->text); - msg->text = g_strdup_printf("", id); - msg->flags |= FB_API_MESSAGE_FLAG_DONE; - - msgs = g_slist_prepend(msgs, msg); - fb_cb_api_messages(api, msgs, fata); - g_slist_free(msgs); + const gchar *url; + FbApi *api; + FbApiMessage *msg; + FbData *fata; + gsize size; + GSList *msgs = NULL; + guint id; + guint8 *image; + PurpleImage *pimg; + + fata = fb_data_image_get_fata(img); + msg = fb_data_image_get_data(img); + + if (G_UNLIKELY(error != NULL)) { + url = fb_data_image_get_url(img); + fb_util_debug_warning("Failed to retrieve image %s: %s", + url, error->message); + return; + } + + api = fb_data_get_api(fata); + image = fb_data_image_dup_image(img, &size); + pimg = purple_image_new_from_data(image, size); + id = purple_image_store_add_weak(pimg); + + g_free(msg->text); + msg->text = g_strdup_printf("", id); + msg->flags |= FB_API_MESSAGE_FLAG_DONE; + + msgs = g_slist_prepend(msgs, msg); + fb_cb_api_messages(api, msgs, fata); + g_slist_free(msgs); } static void fb_cb_api_messages(FbApi *api, GSList *msgs, gpointer data) { - const gchar *text; - FbApiMessage *msg; - FbData *fata = data; - gboolean isself; - gboolean mark; - gboolean open; - gboolean self; - gchar *html; - gchar tid[FB_ID_STRMAX]; - gchar uid[FB_ID_STRMAX]; - gint id; - gint64 tstamp; - GSList *l; - PurpleAccount *acct; - PurpleChatConversation *chat; - PurpleConnection *gc; - PurpleMessageFlags flags; - - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); - mark = purple_account_get_bool(acct, "mark-read", TRUE); - open = purple_account_get_bool(acct, "group-chat-open", TRUE); - self = purple_account_get_bool(acct, "show-self", TRUE); - - for (l = msgs; l != NULL; l = l->next) { - msg = l->data; - FB_ID_TO_STR(msg->uid, uid); - - if (purple_blist_find_buddy(acct, uid) == NULL) { - msg = fb_api_message_dup(msg, TRUE); - fb_data_add_message(fata, msg); - fb_api_contact(api, msg->uid); - continue; - } - - isself = (msg->flags & FB_API_MESSAGE_FLAG_SELF) != 0; - - if (isself && !self) { - continue; - } - - flags = isself ? PURPLE_MESSAGE_SEND : PURPLE_MESSAGE_RECV; - tstamp = msg->tstamp / 1000; - - if (msg->flags & FB_API_MESSAGE_FLAG_IMAGE) { - if (!(msg->flags & FB_API_MESSAGE_FLAG_DONE)) { - msg = fb_api_message_dup(msg, TRUE); - fb_data_image_add(fata, msg->text, fb_cb_image, - msg, (GDestroyNotify) - fb_api_message_free); - fb_data_image_queue(fata); - continue; - } - - flags |= PURPLE_MESSAGE_IMAGES; - text = msg->text; - html = NULL; - } else { - html = purple_markup_escape_text(msg->text, -1); - text = html; - } - - if (msg->tid == 0) { - if (mark && !isself) { - fb_data_set_unread(fata, msg->uid, TRUE); - } - - fb_util_serv_got_im(gc, uid, text, flags, tstamp); - g_free(html); - continue; - } - - FB_ID_TO_STR(msg->tid, tid); - chat = purple_conversations_find_chat_with_account(tid, acct); - - if (chat == NULL) { - if (!open) { - g_free(html); - continue; - } - - id = fb_id_hash(&msg->tid); - purple_serv_got_joined_chat(gc, id, tid); - fb_api_thread(api, msg->tid); - } else { - id = purple_chat_conversation_get_id(chat); - } - - if (mark && !isself) { - fb_data_set_unread(fata, msg->tid, TRUE); - } - - fb_util_serv_got_chat_in(gc, id, uid, text, flags, tstamp); - g_free(html); - } + const gchar *text; + FbApiMessage *msg; + FbData *fata = data; + gboolean isself; + gboolean mark; + gboolean open; + gboolean self; + gchar *html; + gchar tid[FB_ID_STRMAX]; + gchar uid[FB_ID_STRMAX]; + gint id; + gint64 tstamp; + GSList *l; + PurpleAccount *acct; + PurpleChatConversation *chat; + PurpleConnection *gc; + PurpleMessageFlags flags; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + mark = purple_account_get_bool(acct, "mark-read", TRUE); + open = purple_account_get_bool(acct, "group-chat-open", TRUE); + self = purple_account_get_bool(acct, "show-self", TRUE); + + for (l = msgs; l != NULL; l = l->next) { + msg = l->data; + FB_ID_TO_STR(msg->uid, uid); + + if (purple_blist_find_buddy(acct, uid) == NULL) { + msg = fb_api_message_dup(msg, TRUE); + fb_data_add_message(fata, msg); + fb_api_contact(api, msg->uid); + continue; + } + + isself = (msg->flags & FB_API_MESSAGE_FLAG_SELF) != 0; + + if (isself && !self) { + continue; + } + + flags = isself ? PURPLE_MESSAGE_SEND : PURPLE_MESSAGE_RECV; + tstamp = msg->tstamp / 1000; + + if (msg->flags & FB_API_MESSAGE_FLAG_IMAGE) { + if (!(msg->flags & FB_API_MESSAGE_FLAG_DONE)) { + msg = fb_api_message_dup(msg, TRUE); + fb_data_image_add(fata, msg->text, fb_cb_image, + msg, (GDestroyNotify) + fb_api_message_free); + fb_data_image_queue(fata); + continue; + } + + flags |= PURPLE_MESSAGE_IMAGES; + text = msg->text; + html = NULL; + } else { + html = purple_markup_escape_text(msg->text, -1); + text = html; + } + + if (msg->tid == 0) { + if (mark && !isself) { + fb_data_set_unread(fata, msg->uid, TRUE); + } + + fb_util_serv_got_im(gc, uid, text, flags, tstamp); + g_free(html); + continue; + } + + FB_ID_TO_STR(msg->tid, tid); + chat = purple_conversations_find_chat_with_account(tid, acct); + + if (chat == NULL) { + if (!open) { + g_free(html); + continue; + } + + id = fb_id_hash(&msg->tid); + purple_serv_got_joined_chat(gc, id, tid); + fb_api_thread(api, msg->tid); + } else { + id = purple_chat_conversation_get_id(chat); + } + + if (mark && !isself) { + fb_data_set_unread(fata, msg->tid, TRUE); + } + + fb_util_serv_got_chat_in(gc, id, uid, text, flags, tstamp); + g_free(html); + } } static void fb_cb_api_presences(FbApi *api, GSList *press, gpointer data) { - const gchar *statid; - FbApiPresence *pres; - FbData *fata = data; - gchar uid[FB_ID_STRMAX]; - GSList *l; - PurpleAccount *acct; - PurpleConnection *gc; - PurpleStatusPrimitive pstat; - - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); - - for (l = press; l != NULL; l = l->next) { - pres = l->data; - - if (pres->active) { - pstat = PURPLE_STATUS_AVAILABLE; - } else { - pstat = PURPLE_STATUS_OFFLINE; - } - - FB_ID_TO_STR(pres->uid, uid); - statid = purple_primitive_get_id_from_type(pstat); - purple_protocol_got_user_status(acct, uid, statid, NULL); - } + const gchar *statid; + FbApiPresence *pres; + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + GSList *l; + PurpleAccount *acct; + PurpleConnection *gc; + PurpleStatusPrimitive pstat; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + + for (l = press; l != NULL; l = l->next) { + pres = l->data; + + if (pres->active) { + pstat = PURPLE_STATUS_AVAILABLE; + } else { + pstat = PURPLE_STATUS_OFFLINE; + } + + FB_ID_TO_STR(pres->uid, uid); + statid = purple_primitive_get_id_from_type(pstat); + purple_protocol_got_user_status(acct, uid, statid, NULL); + } } static void fb_cb_api_thread(FbApi *api, FbApiThread *thrd, gpointer data) { - const gchar *name; - FbApiUser *user; - FbData *fata = data; - gboolean active; - gchar tid[FB_ID_STRMAX]; - gchar uid[FB_ID_STRMAX]; - gint id; - GSList *l; - PurpleAccount *acct; - PurpleChatConversation *chat; - PurpleConnection *gc; - - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); - id = fb_id_hash(&thrd->tid); - FB_ID_TO_STR(thrd->tid, tid); - - chat = purple_conversations_find_chat_with_account(tid, acct); - - if ((chat == NULL) || purple_chat_conversation_has_left(chat)) { - chat = purple_serv_got_joined_chat(gc, id, tid); - active = FALSE; - } else { - /* If there are no users in the group chat, including - * the local user, then the group chat has yet to be - * setup by this function. As a result, any group chat - * without users is inactive. - */ - active = purple_chat_conversation_get_users_count(chat) > 0; - } - - if (!active) { - name = purple_account_get_username(acct); - purple_chat_conversation_add_user(chat, name, NULL, 0, FALSE); - } - - purple_chat_conversation_set_topic(chat, NULL, thrd->topic); - - for (l = thrd->users; l != NULL; l = l->next) { - user = l->data; - FB_ID_TO_STR(user->uid, uid); - - if (purple_chat_conversation_has_user(chat, uid)) { - continue; - } - - if (purple_blist_find_buddy(acct, uid) == NULL) { - fb_buddy_add_nonfriend(acct, user); - } - - purple_chat_conversation_add_user(chat, uid, NULL, 0, active); - } + const gchar *name; + FbApiUser *user; + FbData *fata = data; + gboolean active; + gchar tid[FB_ID_STRMAX]; + gchar uid[FB_ID_STRMAX]; + gint id; + GSList *l; + PurpleAccount *acct; + PurpleChatConversation *chat; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + id = fb_id_hash(&thrd->tid); + FB_ID_TO_STR(thrd->tid, tid); + + chat = purple_conversations_find_chat_with_account(tid, acct); + + if ((chat == NULL) || purple_chat_conversation_has_left(chat)) { + chat = purple_serv_got_joined_chat(gc, id, tid); + active = FALSE; + } else { + /* If there are no users in the group chat, including + * the local user, then the group chat has yet to be + * setup by this function. As a result, any group chat + * without users is inactive. + */ + active = purple_chat_conversation_get_users_count(chat) > 0; + } + + if (!active) { + name = purple_account_get_username(acct); + purple_chat_conversation_add_user(chat, name, NULL, 0, FALSE); + } + + purple_chat_conversation_set_topic(chat, NULL, thrd->topic); + + for (l = thrd->users; l != NULL; l = l->next) { + user = l->data; + FB_ID_TO_STR(user->uid, uid); + + if (purple_chat_conversation_has_user(chat, uid)) { + continue; + } + + if (purple_blist_find_buddy(acct, uid) == NULL) { + fb_buddy_add_nonfriend(acct, user); + } + + purple_chat_conversation_add_user(chat, uid, NULL, 0, active); + } } static void fb_cb_api_thread_create(FbApi *api, FbId tid, gpointer data) { - FbData *fata = data; - gchar sid[FB_ID_STRMAX]; - GHashTable *table; - PurpleConnection *gc; - - gc = fb_data_get_connection(fata); - FB_ID_TO_STR(tid, sid); - - table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); - g_hash_table_insert(table, "name", g_strdup(sid)); - purple_serv_join_chat(gc, table); - g_hash_table_destroy(table); + FbData *fata = data; + gchar sid[FB_ID_STRMAX]; + GHashTable *table; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + FB_ID_TO_STR(tid, sid); + + table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); + g_hash_table_insert(table, "name", g_strdup(sid)); + purple_serv_join_chat(gc, table); + g_hash_table_destroy(table); } static void fb_cb_api_thread_kicked(FbApi *api, FbApiThread *thrd, gpointer data) { - FbData *fata = data; - gchar tid[FB_ID_STRMAX]; - PurpleAccount *acct; - PurpleConnection *gc; - PurpleChatConversation *chat; - - FB_ID_TO_STR(thrd->tid, tid); - - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); - chat = purple_conversations_find_chat_with_account(tid, acct); - - if (chat == NULL) { - PurpleRequestCommonParameters *cpar; - - cpar = purple_request_cpar_from_connection(gc); - purple_notify_error(gc, - _("Join a Chat"), - _("Failed to Join Chat"), - _("You have been removed from this chat"), - cpar); - return; - } - - purple_conversation_write_system_message(PURPLE_CONVERSATION(chat), - _("You have been removed from this chat"), 0); - - purple_serv_got_chat_left(gc, purple_chat_conversation_get_id(chat)); + FbData *fata = data; + gchar tid[FB_ID_STRMAX]; + PurpleAccount *acct; + PurpleConnection *gc; + PurpleChatConversation *chat; + + FB_ID_TO_STR(thrd->tid, tid); + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + chat = purple_conversations_find_chat_with_account(tid, acct); + + if (chat == NULL) { + PurpleRequestCommonParameters *cpar; + + cpar = purple_request_cpar_from_connection(gc); + purple_notify_error(gc, + _("Join a Chat"), + _("Failed to Join Chat"), + _("You have been removed from this chat"), + cpar); + return; + } + + purple_conversation_write_system_message(PURPLE_CONVERSATION(chat), + _("You have been removed from this chat"), 0); + + purple_serv_got_chat_left(gc, purple_chat_conversation_get_id(chat)); } static void fb_cb_api_threads(FbApi *api, GSList *thrds, gpointer data) { - const gchar *alias; - FbApiUser *user; - FbData *fata = data; - gchar tid[FB_ID_STRMAX]; - gchar uid[FB_ID_STRMAX]; - GSList *l; - GSList *m; - GString *gstr; - FbApiThread *thrd; - PurpleAccount *acct; - PurpleBuddy *bdy; - PurpleConnection *gc; - PurpleRoomlist *list; - PurpleRoomlistRoom *room; - - list = fb_data_get_roomlist(fata); - - if (G_UNLIKELY(list == NULL)) { - return; - } - - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); - gstr = g_string_new(NULL); - - for (l = thrds; l != NULL; l = l->next) { - thrd = l->data; - FB_ID_TO_STR(thrd->tid, tid); - g_string_truncate(gstr, 0); - - for (m = thrd->users; m != NULL; m = m->next) { - user = m->data; - FB_ID_TO_STR(user->uid, uid); - bdy = purple_blist_find_buddy(acct, uid); - - if (bdy != NULL) { - alias = purple_buddy_get_alias(bdy); - } else { - alias = user->name; - } - - if (gstr->len > 0) { - g_string_append(gstr, ", "); - } - - g_string_append(gstr, alias); - } - - room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, - tid, NULL); - purple_roomlist_room_add_field(list, room, thrd->topic); - purple_roomlist_room_add_field(list, room, gstr->str); - purple_roomlist_room_add(list, room); - } - - purple_roomlist_set_in_progress(list, FALSE); - fb_data_set_roomlist(fata, NULL); - g_string_free(gstr, TRUE); + const gchar *alias; + FbApiUser *user; + FbData *fata = data; + gchar tid[FB_ID_STRMAX]; + gchar uid[FB_ID_STRMAX]; + GSList *l; + GSList *m; + GString *gstr; + FbApiThread *thrd; + PurpleAccount *acct; + PurpleBuddy *bdy; + PurpleConnection *gc; + PurpleRoomlist *list; + PurpleRoomlistRoom *room; + + list = fb_data_get_roomlist(fata); + + if (G_UNLIKELY(list == NULL)) { + return; + } + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + gstr = g_string_new(NULL); + + for (l = thrds; l != NULL; l = l->next) { + thrd = l->data; + FB_ID_TO_STR(thrd->tid, tid); + g_string_truncate(gstr, 0); + + for (m = thrd->users; m != NULL; m = m->next) { + user = m->data; + FB_ID_TO_STR(user->uid, uid); + bdy = purple_blist_find_buddy(acct, uid); + + if (bdy != NULL) { + alias = purple_buddy_get_alias(bdy); + } else { + alias = user->name; + } + + if (gstr->len > 0) { + g_string_append(gstr, ", "); + } + + g_string_append(gstr, alias); + } + + room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, + tid, NULL); + purple_roomlist_room_add_field(list, room, thrd->topic); + purple_roomlist_room_add_field(list, room, gstr->str); + purple_roomlist_room_add(list, room); + } + + purple_roomlist_set_in_progress(list, FALSE); + fb_data_set_roomlist(fata, NULL); + g_string_free(gstr, TRUE); } static void fb_cb_api_typing(FbApi *api, FbApiTyping *typg, gpointer data) { - FbData *fata = data; - gchar uid[FB_ID_STRMAX]; - PurpleConnection *gc; - - gc = fb_data_get_connection(fata); - FB_ID_TO_STR(typg->uid, uid); - - if (typg->state) { - purple_serv_got_typing(gc, uid, 0, PURPLE_IM_TYPING); - } else { - purple_serv_got_typing_stopped(gc, uid); - } + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + FB_ID_TO_STR(typg->uid, uid); + + if (typg->state) { + purple_serv_got_typing(gc, uid, 0, PURPLE_IM_TYPING); + } else { + purple_serv_got_typing_stopped(gc, uid); + } } static void fb_mark_read(FbData *fata, FbId id, gboolean thread) { - FbApi *api; - PurpleAccount *acct; - PurpleConnection *gc; - - gc = fb_data_get_connection(fata); - acct = purple_connection_get_account(gc); - api = fb_data_get_api(fata); - - if (!fb_data_get_unread(fata, id) || - (purple_account_get_bool(acct, "mark-read-available", FALSE) && - fb_api_is_invisible(api))) - { - return; - } - - fb_data_set_unread(fata, id, FALSE); - fb_api_read(api, id, thread); + FbApi *api; + PurpleAccount *acct; + PurpleConnection *gc; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + api = fb_data_get_api(fata); + + if (!fb_data_get_unread(fata, id) || + (purple_account_get_bool(acct, "mark-read-available", FALSE) && + fb_api_is_invisible(api))) + { + return; + } + + fb_data_set_unread(fata, id, FALSE); + fb_api_read(api, id, thread); } static gboolean fb_cb_conv_read(gpointer data) { - const gchar *name; - FbData *fata; - FbId id; - gchar *tname; - PurpleConnection *gc; - PurpleConversation *conv = data; - - gc = purple_conversation_get_connection(conv); - fata = purple_connection_get_protocol_data(gc); - name = purple_conversation_get_name(conv); - id = FB_ID_FROM_STR(name); - - tname = g_strconcat("conv-read-", name, NULL); - fb_data_clear_timeout(fata, tname, FALSE); - g_free(tname); - - if (purple_conversation_has_focus(conv)) { - fb_mark_read(fata, id, PURPLE_IS_CHAT_CONVERSATION(conv)); - } - return FALSE; + const gchar *name; + FbData *fata; + FbId id; + gchar *tname; + PurpleConnection *gc; + PurpleConversation *conv = data; + + gc = purple_conversation_get_connection(conv); + fata = purple_connection_get_protocol_data(gc); + name = purple_conversation_get_name(conv); + id = FB_ID_FROM_STR(name); + + tname = g_strconcat("conv-read-", name, NULL); + fb_data_clear_timeout(fata, tname, FALSE); + g_free(tname); + + if (purple_conversation_has_focus(conv)) { + fb_mark_read(fata, id, PURPLE_IS_CHAT_CONVERSATION(conv)); + } + return FALSE; } static void fb_cb_conv_updated(PurpleConversation *conv, PurpleConversationUpdateType type, gpointer data) { - const gchar *name; - const gchar *pid; - FbData *fata = data; - gchar *tname; - PurpleAccount *acct; - - acct = purple_conversation_get_account(conv); - pid = purple_account_get_protocol_id(acct); - - if ((type == PURPLE_CONVERSATION_UPDATE_UNSEEN) && - purple_strequal(pid, FB_PROTOCOL_ID) && - purple_account_get_bool(acct, "mark-read", TRUE)) - { - /* Use event loop for purple_conversation_has_focus() */ - name = purple_conversation_get_name(conv); - tname = g_strconcat("conv-read-", name, NULL); - fb_data_add_timeout(fata, tname, 1, fb_cb_conv_read, conv); - g_free(tname); - } + const gchar *name; + const gchar *pid; + FbData *fata = data; + gchar *tname; + PurpleAccount *acct; + + acct = purple_conversation_get_account(conv); + pid = purple_account_get_protocol_id(acct); + + if ((type == PURPLE_CONVERSATION_UPDATE_UNSEEN) && + purple_strequal(pid, FB_PROTOCOL_ID) && + purple_account_get_bool(acct, "mark-read", TRUE)) + { + /* Use event loop for purple_conversation_has_focus() */ + name = purple_conversation_get_name(conv); + tname = g_strconcat("conv-read-", name, NULL); + fb_data_add_timeout(fata, tname, 1, fb_cb_conv_read, conv); + g_free(tname); + } } static void fb_cb_conv_deleting(PurpleConversation *conv, gpointer data) { - const gchar *name; - const gchar *pid; - FbData *fata = data; - gchar *tname; - PurpleAccount *acct; - - acct = purple_conversation_get_account(conv); - pid = purple_account_get_protocol_id(acct); - - if (!purple_strequal(pid, FB_PROTOCOL_ID)) { - return; - } - - name = purple_conversation_get_name(conv); - tname = g_strconcat("conv-read-", name, NULL); - fb_data_clear_timeout(fata, tname, TRUE); - g_free(tname); + const gchar *name; + const gchar *pid; + FbData *fata = data; + gchar *tname; + PurpleAccount *acct; + + acct = purple_conversation_get_account(conv); + pid = purple_account_get_protocol_id(acct); + + if (!purple_strequal(pid, FB_PROTOCOL_ID)) { + return; + } + + name = purple_conversation_get_name(conv); + tname = g_strconcat("conv-read-", name, NULL); + fb_data_clear_timeout(fata, tname, TRUE); + g_free(tname); } static void fb_blist_chat_create(GSList *buddies, gpointer data) { - const gchar *name; - FbApi *api; - FbData *fata = data; - FbId *did; - FbId uid; - GSList *l; - GSList *uids = NULL; - PurpleConnection *gc; - PurpleRequestCommonParameters *cpar; - - gc = fb_data_get_connection(fata); - api = fb_data_get_api(fata); - - if (g_slist_length(buddies) < 2) { - cpar = purple_request_cpar_from_connection(gc); - purple_notify_error(gc, - _("Initiate Chat"), - _("Failed to Initiate Chat"), - _("At least two initial chat participants" - " are required."), - cpar); - return; - } - - for (l = buddies; l != NULL; l = l->next) { - name = purple_buddy_get_name(l->data); - uid = FB_ID_FROM_STR(name); - did = g_memdup2(&uid, sizeof uid); - uids = g_slist_prepend(uids, did); - } - - fb_api_thread_create(api, uids); - g_slist_free_full(uids, g_free); + const gchar *name; + FbApi *api; + FbData *fata = data; + FbId *did; + FbId uid; + GSList *l; + GSList *uids = NULL; + PurpleConnection *gc; + PurpleRequestCommonParameters *cpar; + + gc = fb_data_get_connection(fata); + api = fb_data_get_api(fata); + + if (g_slist_length(buddies) < 2) { + cpar = purple_request_cpar_from_connection(gc); + purple_notify_error(gc, + _("Initiate Chat"), + _("Failed to Initiate Chat"), + _("At least two initial chat participants" + " are required."), + cpar); + return; + } + + for (l = buddies; l != NULL; l = l->next) { + name = purple_buddy_get_name(l->data); + uid = FB_ID_FROM_STR(name); + did = g_memdup2(&uid, sizeof uid); + uids = g_slist_prepend(uids, did); + } + + fb_api_thread_create(api, uids); + g_slist_free_full(uids, g_free); } static void fb_blist_chat_init(PurpleBlistNode *node, gpointer data) { - FbData *fata = data; - GSList *select = NULL; - PurpleConnection *gc; - - if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { - return; - } - - gc = fb_data_get_connection(fata); - select = g_slist_prepend(select, PURPLE_BUDDY(node)); - - fb_util_request_buddy(gc, - _("Initiate Chat"), - _("Initial Chat Participants"), - _("Select at least two initial participants."), - select, TRUE, - G_CALLBACK(fb_blist_chat_create), NULL, - fata); - g_slist_free(select); + FbData *fata = data; + GSList *select = NULL; + PurpleConnection *gc; + + if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { + return; + } + + gc = fb_data_get_connection(fata); + select = g_slist_prepend(select, PURPLE_BUDDY(node)); + + fb_util_request_buddy(gc, + _("Initiate Chat"), + _("Initial Chat Participants"), + _("Select at least two initial participants."), + select, TRUE, + G_CALLBACK(fb_blist_chat_create), NULL, + fata); + g_slist_free(select); } static void fb_login(PurpleAccount *acct) { - const gchar *pass; - const gchar *user; - FbApi *api; - FbData *fata; - gpointer convh; - PurpleConnection *gc; - - gc = purple_account_get_connection(acct); - - fata = fb_data_new(gc); - api = fb_data_get_api(fata); - convh = purple_conversations_get_handle(); - purple_connection_set_protocol_data(gc, fata); - - g_signal_connect(api, - "auth", - G_CALLBACK(fb_cb_api_auth), - fata); - g_signal_connect(api, - "connect", - G_CALLBACK(fb_cb_api_connect), - fata); - g_signal_connect(api, - "contact", - G_CALLBACK(fb_cb_api_contact), - fata); - g_signal_connect(api, - "contacts", - G_CALLBACK(fb_cb_api_contacts), - fata); - g_signal_connect(api, - "contacts-delta", - G_CALLBACK(fb_cb_api_contacts_delta), - fata); - g_signal_connect(api, - "error", - G_CALLBACK(fb_cb_api_error), - fata); - g_signal_connect(api, - "events", - G_CALLBACK(fb_cb_api_events), - fata); - g_signal_connect(api, - "messages", - G_CALLBACK(fb_cb_api_messages), - fata); - g_signal_connect(api, - "presences", - G_CALLBACK(fb_cb_api_presences), - fata); - g_signal_connect(api, - "thread", - G_CALLBACK(fb_cb_api_thread), - fata); - g_signal_connect(api, - "thread-create", - G_CALLBACK(fb_cb_api_thread_create), - fata); - g_signal_connect(api, - "thread-kicked", - G_CALLBACK(fb_cb_api_thread_kicked), - fata); - g_signal_connect(api, - "threads", - G_CALLBACK(fb_cb_api_threads), - fata); - g_signal_connect(api, - "typing", - G_CALLBACK(fb_cb_api_typing), - fata); - - purple_signal_connect(convh, - "conversation-updated", - gc, - G_CALLBACK(fb_cb_conv_updated), - fata); - purple_signal_connect(convh, - "deleting-conversation", - gc, - G_CALLBACK(fb_cb_conv_deleting), - fata); - - if (!fb_data_load(fata) || !purple_account_get_remember_password(acct)) { - user = purple_account_get_username(acct); - pass = purple_connection_get_password(gc); - purple_connection_update_progress(gc, _("Authenticating"), - 1, 4); - fb_api_auth(api, user, pass); - return; - } - - purple_connection_update_progress(gc, _("Fetching contacts"), 2, 4); - fb_api_contacts(api); + const gchar *pass; + const gchar *user; + FbApi *api; + FbData *fata; + gpointer convh; + PurpleConnection *gc; + + gc = purple_account_get_connection(acct); + + fata = fb_data_new(gc); + api = fb_data_get_api(fata); + convh = purple_conversations_get_handle(); + purple_connection_set_protocol_data(gc, fata); + + g_signal_connect(api, + "auth", + G_CALLBACK(fb_cb_api_auth), + fata); + g_signal_connect(api, + "connect", + G_CALLBACK(fb_cb_api_connect), + fata); + g_signal_connect(api, + "contact", + G_CALLBACK(fb_cb_api_contact), + fata); + g_signal_connect(api, + "contacts", + G_CALLBACK(fb_cb_api_contacts), + fata); + g_signal_connect(api, + "contacts-delta", + G_CALLBACK(fb_cb_api_contacts_delta), + fata); + g_signal_connect(api, + "error", + G_CALLBACK(fb_cb_api_error), + fata); + g_signal_connect(api, + "events", + G_CALLBACK(fb_cb_api_events), + fata); + g_signal_connect(api, + "messages", + G_CALLBACK(fb_cb_api_messages), + fata); + g_signal_connect(api, + "presences", + G_CALLBACK(fb_cb_api_presences), + fata); + g_signal_connect(api, + "thread", + G_CALLBACK(fb_cb_api_thread), + fata); + g_signal_connect(api, + "thread-create", + G_CALLBACK(fb_cb_api_thread_create), + fata); + g_signal_connect(api, + "thread-kicked", + G_CALLBACK(fb_cb_api_thread_kicked), + fata); + g_signal_connect(api, + "threads", + G_CALLBACK(fb_cb_api_threads), + fata); + g_signal_connect(api, + "typing", + G_CALLBACK(fb_cb_api_typing), + fata); + + purple_signal_connect(convh, + "conversation-updated", + gc, + G_CALLBACK(fb_cb_conv_updated), + fata); + purple_signal_connect(convh, + "deleting-conversation", + gc, + G_CALLBACK(fb_cb_conv_deleting), + fata); + + if (!fb_data_load(fata) || !purple_account_get_remember_password(acct)) { + user = purple_account_get_username(acct); + pass = purple_connection_get_password(gc); + purple_connection_update_progress(gc, _("Authenticating"), + 1, 4); + fb_api_auth(api, user, pass, NULL); + return; + } + + purple_connection_update_progress(gc, _("Fetching contacts"), 2, 4); + fb_api_contacts(api); } static void fb_close(PurpleConnection *gc) { - FbApi *api; - FbData *fata; + FbApi *api; + FbData *fata; - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); - fb_data_save(fata); - fb_api_disconnect(api); - g_object_unref(fata); + fb_data_save(fata); + fb_api_disconnect(api); + g_object_unref(fata); - purple_connection_set_protocol_data(gc, NULL); - purple_signals_disconnect_by_handle(gc); + purple_connection_set_protocol_data(gc, NULL); + purple_signals_disconnect_by_handle(gc); } static GList * fb_status_types(PurpleAccount *acct) { - PurpleStatusType *type; - GList *types = NULL; + PurpleStatusType *type; + GList *types = NULL; - type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, - NULL, NULL, TRUE); - types = g_list_prepend(types, type); + type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, + NULL, NULL, TRUE); + types = g_list_prepend(types, type); - /* Just a NULL state (as of now) for compatibility */ - type = purple_status_type_new(PURPLE_STATUS_AWAY, - NULL, NULL, TRUE); - types = g_list_prepend(types, type); + /* Just a NULL state (as of now) for compatibility */ + type = purple_status_type_new(PURPLE_STATUS_AWAY, + NULL, NULL, TRUE); + types = g_list_prepend(types, type); - type = purple_status_type_new(PURPLE_STATUS_INVISIBLE, - NULL, NULL, TRUE); - types = g_list_prepend(types, type); + type = purple_status_type_new(PURPLE_STATUS_INVISIBLE, + NULL, NULL, TRUE); + types = g_list_prepend(types, type); - type = purple_status_type_new(PURPLE_STATUS_OFFLINE, - NULL, NULL, TRUE); - types = g_list_prepend(types, type); + type = purple_status_type_new(PURPLE_STATUS_OFFLINE, + NULL, NULL, TRUE); + types = g_list_prepend(types, type); - return g_list_reverse(types); + return g_list_reverse(types); } static const char * fb_list_icon(PurpleAccount *account, PurpleBuddy *buddy) { - return "facebook"; + return "facebook"; } static void fb_client_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *info, gboolean full) { - const gchar *name; - PurplePresence *pres; - PurpleStatus *status; + const gchar *name; + PurplePresence *pres; + PurpleStatus *status; - pres = purple_buddy_get_presence(buddy); - status = purple_presence_get_active_status(pres); + pres = purple_buddy_get_presence(buddy); + status = purple_presence_get_active_status(pres); - if (!PURPLE_BUDDY_IS_ONLINE(buddy)) { - /* Prevent doubles statues for Offline buddies */ - /* See: pidgin_get_tooltip_text() in gtkblist.c */ - purple_notify_user_info_remove_last_item(info); - } + if (!PURPLE_BUDDY_IS_ONLINE(buddy)) { + /* Prevent doubles statues for Offline buddies */ + /* See: pidgin_get_tooltip_text() in gtkblist.c */ + purple_notify_user_info_remove_last_item(info); + } - name = purple_status_get_name(status); - purple_notify_user_info_add_pair_plaintext(info, _("Status"), name); + name = purple_status_get_name(status); + purple_notify_user_info_add_pair_plaintext(info, _("Status"), name); } static GList * fb_client_blist_node_menu(PurpleBlistNode *node) { - FbData *fata; - GList *acts = NULL; - PurpleAccount *acct; - PurpleConnection *gc; - PurpleMenuAction *act; - - if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { - return NULL; - } - - acct = purple_buddy_get_account(PURPLE_BUDDY(node)); - gc = purple_account_get_connection(acct); - fata = purple_connection_get_protocol_data(gc); - - act = purple_menu_action_new(_("Initiate _Chat"), - PURPLE_CALLBACK(fb_blist_chat_init), - fata, NULL); - acts = g_list_prepend(acts, act); - - return g_list_reverse(acts); + FbData *fata; + GList *acts = NULL; + PurpleAccount *acct; + PurpleConnection *gc; + PurpleMenuAction *act; + + if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { + return NULL; + } + + acct = purple_buddy_get_account(PURPLE_BUDDY(node)); + gc = purple_account_get_connection(acct); + fata = purple_connection_get_protocol_data(gc); + + act = purple_menu_action_new(_("Initiate _Chat"), + PURPLE_CALLBACK(fb_blist_chat_init), + fata, NULL); + acts = g_list_prepend(acts, act); + + return g_list_reverse(acts); } static gboolean fb_client_offline_message(const PurpleBuddy *buddy) { - return TRUE; + return TRUE; } static void fb_server_set_status(PurpleAccount *acct, PurpleStatus *status) { - FbApi *api; - FbData *fata; - gboolean invis; - PurpleConnection *gc; - PurpleStatusPrimitive pstat; - PurpleStatusType *type; - - gc = purple_account_get_connection(acct); - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); - - type = purple_status_get_status_type(status); - pstat = purple_status_type_get_primitive(type); - invis = fb_api_is_invisible(api); - - if ((pstat == PURPLE_STATUS_INVISIBLE) && !invis) { - fb_api_connect(api, TRUE); - } else if ((pstat != PURPLE_STATUS_OFFLINE) && invis) { - fb_api_connect(api, FALSE); - } + FbApi *api; + FbData *fata; + gboolean invis; + PurpleConnection *gc; + PurpleStatusPrimitive pstat; + PurpleStatusType *type; + + gc = purple_account_get_connection(acct); + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + + type = purple_status_get_status_type(status); + pstat = purple_status_type_get_primitive(type); + invis = fb_api_is_invisible(api); + + if ((pstat == PURPLE_STATUS_INVISIBLE) && !invis) { + fb_api_connect(api, TRUE); + } else if ((pstat != PURPLE_STATUS_OFFLINE) && invis) { + fb_api_connect(api, FALSE); + } } static gint fb_im_send(PurpleConnection *gc, const gchar *who, const gchar *tmsg, PurpleMessageFlags flags) { - const gchar *name; - const gchar *text; - FbApi *api; - FbData *fata; - FbId uid; - gchar *sext; - - PurpleMessage *msg = purple_message_new_outgoing(who, tmsg, flags); - - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); - - name = purple_message_get_recipient(msg); - uid = FB_ID_FROM_STR(name); - - text = purple_message_get_contents(msg); - sext = purple_markup_strip_html(text); - fb_api_message(api, uid, FALSE, sext); - g_free(sext); - return 1; + const gchar *name; + const gchar *text; + FbApi *api; + FbData *fata; + FbId uid; + gchar *sext; + + PurpleMessage *msg = purple_message_new_outgoing(who, tmsg, flags); + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + + name = purple_message_get_recipient(msg); + uid = FB_ID_FROM_STR(name); + + text = purple_message_get_contents(msg); + sext = purple_markup_strip_html(text); + fb_api_message(api, uid, FALSE, sext); + g_free(sext); + return 1; } static guint fb_im_send_typing(PurpleConnection *gc, const gchar *name, PurpleIMTypingState state) { - FbApi *api; - FbData *fata; - FbId uid; + FbApi *api; + FbData *fata; + FbId uid; - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); - uid = FB_ID_FROM_STR(name); + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + uid = FB_ID_FROM_STR(name); - fb_api_typing(api, uid, state != PURPLE_IM_NOT_TYPING); - return 0; + fb_api_typing(api, uid, state != PURPLE_IM_NOT_TYPING); + return 0; } static GList * fb_chat_info() { - GList *pces = NULL; - PurpleProtocolChatEntry *pce; + GList *pces = NULL; + PurpleProtocolChatEntry *pce; - pce = g_new0(PurpleProtocolChatEntry, 1); - pce->label = _("Chat _Name:"); - pce->identifier = "name"; - pce->required = TRUE; - pces = g_list_prepend(pces, pce); + pce = g_new0(PurpleProtocolChatEntry, 1); + pce->label = _("Chat _Name:"); + pce->identifier = "name"; + pce->required = TRUE; + pces = g_list_prepend(pces, pce); - return g_list_reverse(pces); + return g_list_reverse(pces); } static GHashTable * fb_chat_info_defaults(PurpleConnection *gc, const gchar *name) { - GHashTable *data; + GHashTable *data; - data = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); - g_hash_table_insert(data, "name", g_strdup(name)); + data = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); + g_hash_table_insert(data, "name", g_strdup(name)); - return data; + return data; } static void fb_chat_join(PurpleConnection *gc, GHashTable *data) { - const gchar *name; - FbApi *api; - FbData *fata; - FbId tid; - gint id; - PurpleChatConversation *chat; - PurpleRequestCommonParameters *cpar; - - name = g_hash_table_lookup(data, "name"); - g_return_if_fail(name != NULL); - - if (!FB_ID_IS_STR(name)) { - cpar = purple_request_cpar_from_connection(gc); - purple_notify_error(gc, - _("Join a Chat"), - _("Failed to Join Chat"), - _("Invalid Facebook identifier."), - cpar); - return; - } - - tid = FB_ID_FROM_STR(name); - id = fb_id_hash(&tid); - chat = purple_conversations_find_chat(gc, id); - - if ((chat != NULL) && !purple_chat_conversation_has_left(chat)) { - purple_conversation_present(PURPLE_CONVERSATION(chat)); - return; - } - - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); - fb_api_thread(api, tid); + const gchar *name; + FbApi *api; + FbData *fata; + FbId tid; + gint id; + PurpleChatConversation *chat; + PurpleRequestCommonParameters *cpar; + + name = g_hash_table_lookup(data, "name"); + g_return_if_fail(name != NULL); + + if (!FB_ID_IS_STR(name)) { + cpar = purple_request_cpar_from_connection(gc); + purple_notify_error(gc, + _("Join a Chat"), + _("Failed to Join Chat"), + _("Invalid Facebook identifier."), + cpar); + return; + } + + tid = FB_ID_FROM_STR(name); + id = fb_id_hash(&tid); + chat = purple_conversations_find_chat(gc, id); + + if ((chat != NULL) && !purple_chat_conversation_has_left(chat)) { + purple_conversation_present(PURPLE_CONVERSATION(chat)); + return; + } + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + fb_api_thread(api, tid); } static gchar * fb_chat_get_name(GHashTable *data) { - const gchar *name; + const gchar *name; - name = g_hash_table_lookup(data, "name"); - g_return_val_if_fail(name != NULL, NULL); + name = g_hash_table_lookup(data, "name"); + g_return_val_if_fail(name != NULL, NULL); - return g_strdup(name); + return g_strdup(name); } static void fb_chat_invite(PurpleConnection *gc, gint id, const gchar *msg, const gchar *who) { - const gchar *name; - FbApi *api; - FbData *fata; - FbId tid; - FbId uid; - PurpleChatConversation *chat; - PurpleRequestCommonParameters *cpar; - - if (!FB_ID_IS_STR(who)) { - cpar = purple_request_cpar_from_connection(gc); - purple_notify_error(gc, - _("Invite Buddy Into Chat Room"), - _("Failed to Invite User"), - _("Invalid Facebook identifier."), - cpar); - return; - } - - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); - chat = purple_conversations_find_chat(gc, id); - - name = purple_conversation_get_name(PURPLE_CONVERSATION(chat)); - tid = FB_ID_FROM_STR(name); - uid = FB_ID_FROM_STR(who); - - fb_api_thread_invite(api, tid, uid); + const gchar *name; + FbApi *api; + FbData *fata; + FbId tid; + FbId uid; + PurpleChatConversation *chat; + PurpleRequestCommonParameters *cpar; + + if (!FB_ID_IS_STR(who)) { + cpar = purple_request_cpar_from_connection(gc); + purple_notify_error(gc, + _("Invite Buddy Into Chat Room"), + _("Failed to Invite User"), + _("Invalid Facebook identifier."), + cpar); + return; + } + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + chat = purple_conversations_find_chat(gc, id); + + name = purple_conversation_get_name(PURPLE_CONVERSATION(chat)); + tid = FB_ID_FROM_STR(name); + uid = FB_ID_FROM_STR(who); + + fb_api_thread_invite(api, tid, uid); } static gint fb_chat_send(PurpleConnection *gc, gint id, const gchar *tmsg, PurpleMessageFlags flags) { - const gchar *name; - const gchar *text; - FbApi *api; - FbData *fata; - FbId tid; - gchar *sext; - PurpleAccount *acct; - PurpleChatConversation *chat; - - PurpleMessage *msg = purple_message_new_outgoing(NULL, tmsg, flags); - - acct = purple_connection_get_account(gc); - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); - chat = purple_conversations_find_chat(gc, id); - - name = purple_conversation_get_name(PURPLE_CONVERSATION(chat)); - tid = FB_ID_FROM_STR(name); - - text = purple_message_get_contents(msg); - sext = purple_markup_strip_html(text); - fb_api_message(api, tid, TRUE, sext); - g_free(sext); - - name = purple_account_get_username(acct); - purple_serv_got_chat_in(gc, id, name, - purple_message_get_flags(msg), - purple_message_get_contents(msg), - time(NULL)); - return 0; + const gchar *name; + const gchar *text; + FbApi *api; + FbData *fata; + FbId tid; + gchar *sext; + PurpleAccount *acct; + PurpleChatConversation *chat; + + PurpleMessage *msg = purple_message_new_outgoing(NULL, tmsg, flags); + + acct = purple_connection_get_account(gc); + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + chat = purple_conversations_find_chat(gc, id); + + name = purple_conversation_get_name(PURPLE_CONVERSATION(chat)); + tid = FB_ID_FROM_STR(name); + + text = purple_message_get_contents(msg); + sext = purple_markup_strip_html(text); + fb_api_message(api, tid, TRUE, sext); + g_free(sext); + + name = purple_account_get_username(acct); + purple_serv_got_chat_in(gc, id, name, + purple_message_get_flags(msg), + purple_message_get_contents(msg), + time(NULL)); + return 0; } static void fb_chat_set_topic(PurpleConnection *gc, gint id, const gchar *topic) { - const gchar *name; - FbApi *api; - FbData *fata; - FbId tid; - PurpleChatConversation *chat; - - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); - chat = purple_conversations_find_chat(gc, id); - - name = purple_conversation_get_name(PURPLE_CONVERSATION(chat)); - tid = FB_ID_FROM_STR(name); - fb_api_thread_topic(api, tid, topic); + const gchar *name; + FbApi *api; + FbData *fata; + FbId tid; + PurpleChatConversation *chat; + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + chat = purple_conversations_find_chat(gc, id); + + name = purple_conversation_get_name(PURPLE_CONVERSATION(chat)); + tid = FB_ID_FROM_STR(name); + fb_api_thread_topic(api, tid, topic); } static PurpleRoomlist * fb_roomlist_get_list(PurpleConnection *gc) { - FbApi *api; - FbData *fata; - GList *flds = NULL; - PurpleAccount *acct; - PurpleRoomlist *list; - PurpleRoomlistField *fld; - - fata = purple_connection_get_protocol_data(gc); - list = fb_data_get_roomlist(fata); - g_return_val_if_fail(list == NULL, NULL); - - api = fb_data_get_api(fata); - acct = purple_connection_get_account(gc); - list = purple_roomlist_new(acct); - fb_data_set_roomlist(fata, list); - - fld = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, - _("Topic"), "topic", FALSE); - flds = g_list_prepend(flds, fld); - - fld = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, - _("Users"), "users", FALSE); - flds = g_list_prepend(flds, fld); - - flds = g_list_reverse(flds); - purple_roomlist_set_fields(list, flds); - - purple_roomlist_set_in_progress(list, TRUE); - fb_api_threads(api); - return list; + FbApi *api; + FbData *fata; + GList *flds = NULL; + PurpleAccount *acct; + PurpleRoomlist *list; + PurpleRoomlistField *fld; + + fata = purple_connection_get_protocol_data(gc); + list = fb_data_get_roomlist(fata); + g_return_val_if_fail(list == NULL, NULL); + + api = fb_data_get_api(fata); + acct = purple_connection_get_account(gc); + list = purple_roomlist_new(acct); + fb_data_set_roomlist(fata, list); + + fld = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, + _("Topic"), "topic", FALSE); + flds = g_list_prepend(flds, fld); + + fld = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, + _("Users"), "users", FALSE); + flds = g_list_prepend(flds, fld); + + flds = g_list_reverse(flds); + purple_roomlist_set_fields(list, flds); + + purple_roomlist_set_in_progress(list, TRUE); + fb_api_threads(api); + return list; } static void fb_roomlist_cancel(PurpleRoomlist *list) { - FbData *fata; - PurpleAccount *acct; - PurpleConnection *gc; - PurpleRoomlist *cist; - - acct = purple_roomlist_get_account(list); - gc = purple_account_get_connection(acct); - fata = purple_connection_get_protocol_data(gc); - cist = fb_data_get_roomlist(fata); - - if (G_LIKELY(cist == list)) { - fb_data_set_roomlist(fata, NULL); - } - - purple_roomlist_set_in_progress(list, FALSE); - g_object_unref(list); + FbData *fata; + PurpleAccount *acct; + PurpleConnection *gc; + PurpleRoomlist *cist; + + acct = purple_roomlist_get_account(list); + gc = purple_account_get_connection(acct); + fata = purple_connection_get_protocol_data(gc); + cist = fb_data_get_roomlist(fata); + + if (G_LIKELY(cist == list)) { + fb_data_set_roomlist(fata, NULL); + } + + purple_roomlist_set_in_progress(list, FALSE); + g_object_unref(list); } static PurpleCmdRet fb_cmd_kick(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, gpointer data) { - const gchar *name; - FbApi *api; - FbData *fata; - FbId tid; - FbId uid; - GError *err = NULL; - PurpleAccount *acct; - PurpleBuddy *bdy; - PurpleConnection *gc; - PurpleChatConversation *chat; - - g_return_val_if_fail(PURPLE_IS_CHAT_CONVERSATION(conv), - PURPLE_CMD_RET_FAILED); - - gc = purple_conversation_get_connection(conv); - acct = purple_connection_get_account(gc); - chat = PURPLE_CHAT_CONVERSATION(conv); - bdy = fb_util_account_find_buddy(acct, chat, args[0], &err); - - if (err != NULL) { - *error = g_strdup_printf(_("%s."), err->message); - g_error_free(err); - return PURPLE_CMD_RET_FAILED; - } - - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); - - name = purple_conversation_get_name(conv); - tid = FB_ID_FROM_STR(name); - - name = purple_buddy_get_name(bdy); - uid = FB_ID_FROM_STR(name); - - fb_api_thread_remove(api, tid, uid); - return PURPLE_CMD_RET_OK; + const gchar *name; + FbApi *api; + FbData *fata; + FbId tid; + FbId uid; + GError *err = NULL; + PurpleAccount *acct; + PurpleBuddy *bdy; + PurpleConnection *gc; + PurpleChatConversation *chat; + + g_return_val_if_fail(PURPLE_IS_CHAT_CONVERSATION(conv), + PURPLE_CMD_RET_FAILED); + + gc = purple_conversation_get_connection(conv); + acct = purple_connection_get_account(gc); + chat = PURPLE_CHAT_CONVERSATION(conv); + bdy = fb_util_account_find_buddy(acct, chat, args[0], &err); + + if (err != NULL) { + *error = g_strdup_printf(_("%s."), err->message); + g_error_free(err); + return PURPLE_CMD_RET_FAILED; + } + + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + + name = purple_conversation_get_name(conv); + tid = FB_ID_FROM_STR(name); + + name = purple_buddy_get_name(bdy); + uid = FB_ID_FROM_STR(name); + + fb_api_thread_remove(api, tid, uid); + return PURPLE_CMD_RET_OK; } static PurpleCmdRet fb_cmd_leave(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, gpointer data) { - const gchar *name; - FbApi *api; - FbData *fata; - FbId tid; - gint id; - PurpleConnection *gc; - PurpleChatConversation *chat; - - g_return_val_if_fail(PURPLE_IS_CHAT_CONVERSATION(conv), - PURPLE_CMD_RET_FAILED); - - gc = purple_conversation_get_connection(conv); - fata = purple_connection_get_protocol_data(gc); - api = fb_data_get_api(fata); - - chat = PURPLE_CHAT_CONVERSATION(conv); - id = purple_chat_conversation_get_id(chat); - - name = purple_conversation_get_name(conv); - tid = FB_ID_FROM_STR(name); - - purple_serv_got_chat_left(gc, id); - fb_api_thread_remove(api, tid, 0); - return PURPLE_CMD_RET_OK; + const gchar *name; + FbApi *api; + FbData *fata; + FbId tid; + gint id; + PurpleConnection *gc; + PurpleChatConversation *chat; + + g_return_val_if_fail(PURPLE_IS_CHAT_CONVERSATION(conv), + PURPLE_CMD_RET_FAILED); + + gc = purple_conversation_get_connection(conv); + fata = purple_connection_get_protocol_data(gc); + api = fb_data_get_api(fata); + + chat = PURPLE_CHAT_CONVERSATION(conv); + id = purple_chat_conversation_get_id(chat); + + name = purple_conversation_get_name(conv); + tid = FB_ID_FROM_STR(name); + + purple_serv_got_chat_left(gc, id); + fb_api_thread_remove(api, tid, 0); + return PURPLE_CMD_RET_OK; } static void fb_cmds_register(void) { - PurpleCmdId id; + PurpleCmdId id; - static PurpleCmdFlag cflags = - PURPLE_CMD_FLAG_CHAT | - PURPLE_CMD_FLAG_PROTOCOL_ONLY; + static PurpleCmdFlag cflags = + PURPLE_CMD_FLAG_CHAT | + PURPLE_CMD_FLAG_PROTOCOL_ONLY; - g_return_if_fail(fb_cmds == NULL); + g_return_if_fail(fb_cmds == NULL); - id = purple_cmd_register("kick", "s", PURPLE_CMD_P_PROTOCOL, cflags, - "prpl-facebook", fb_cmd_kick, - _("kick: Kick someone from the chat"), - NULL); - fb_cmds = g_slist_prepend(fb_cmds, GUINT_TO_POINTER(id)); + id = purple_cmd_register("kick", "s", PURPLE_CMD_P_PROTOCOL, cflags, + "prpl-facebook", fb_cmd_kick, + _("kick: Kick someone from the chat"), + NULL); + fb_cmds = g_slist_prepend(fb_cmds, GUINT_TO_POINTER(id)); - id = purple_cmd_register("leave", "", PURPLE_CMD_P_PROTOCOL, cflags, - "prpl-facebook", fb_cmd_leave, - _("leave: Leave the chat"), - NULL); - fb_cmds = g_slist_prepend(fb_cmds, GUINT_TO_POINTER(id)); + id = purple_cmd_register("leave", "", PURPLE_CMD_P_PROTOCOL, cflags, + "prpl-facebook", fb_cmd_leave, + _("leave: Leave the chat"), + NULL); + fb_cmds = g_slist_prepend(fb_cmds, GUINT_TO_POINTER(id)); } static void fb_cmds_unregister_free(gpointer data) { - PurpleCmdId id = GPOINTER_TO_UINT(data); - purple_cmd_unregister(id); + PurpleCmdId id = GPOINTER_TO_UINT(data); + purple_cmd_unregister(id); } static void fb_cmds_unregister(void) { - g_slist_free_full(fb_cmds, fb_cmds_unregister_free); + g_slist_free_full(fb_cmds, fb_cmds_unregister_free); } static gboolean plugin_load(PurplePlugin *plugin) { - fb_cmds_register(); - _purple_socket_init(); - purple_http_init(); - return TRUE; + fb_cmds_register(); + _purple_socket_init(); + purple_http_init(); + return TRUE; } static gboolean plugin_unload(PurplePlugin *plugin) { - fb_cmds_unregister(); - purple_http_uninit(); - _purple_socket_uninit(); - return TRUE; + fb_cmds_unregister(); + purple_http_uninit(); + _purple_socket_uninit(); + return TRUE; } G_MODULE_EXPORT gboolean @@ -1600,86 +1600,86 @@ purple_init_plugin(PurplePlugin *plugin); G_MODULE_EXPORT gboolean purple_init_plugin(PurplePlugin *plugin) { - GList *opts = NULL; - PurpleAccountOption *opt; - - static gboolean inited = FALSE; - static PurplePluginInfo info; - static PurplePluginProtocolInfo pinfo; - - (void) fb_protocol; - plugin->info = &info; - - if (G_LIKELY(inited)) { - return purple_plugin_register(plugin); - } - - memset(&info, 0, sizeof info); - memset(&pinfo, 0, sizeof pinfo); - - info.magic = PURPLE_PLUGIN_MAGIC; - info.major_version = PURPLE_MAJOR_VERSION; - info.minor_version = PURPLE_MINOR_VERSION; - info.type = PURPLE_PLUGIN_PROTOCOL; - info.priority = PURPLE_PRIORITY_DEFAULT; - info.id = FB_PROTOCOL_ID; - info.name = "Facebook"; - info.version = PACKAGE_VERSION; - info.summary = N_("Facebook Protocol Plugin"); - info.description = N_("Facebook Protocol Plugin"); - info.homepage = PACKAGE_URL; - info.load = plugin_load; - info.unload = plugin_unload; - info.extra_info = &pinfo; - - pinfo.options = OPT_PROTO_CHAT_TOPIC; - pinfo.list_icon = fb_list_icon; - pinfo.tooltip_text = fb_client_tooltip_text; - pinfo.status_types = fb_status_types; - pinfo.blist_node_menu = fb_client_blist_node_menu; - pinfo.chat_info = fb_chat_info; - pinfo.chat_info_defaults = fb_chat_info_defaults; - pinfo.login = fb_login; - pinfo.close = fb_close; - pinfo.send_im = fb_im_send; - pinfo.send_typing = fb_im_send_typing; - pinfo.set_status = fb_server_set_status; - pinfo.join_chat = fb_chat_join; - pinfo.get_chat_name = fb_chat_get_name; - pinfo.chat_invite = fb_chat_invite; - pinfo.chat_send = fb_chat_send; - pinfo.set_chat_topic = fb_chat_set_topic; - pinfo.roomlist_get_list = fb_roomlist_get_list; - pinfo.roomlist_cancel = fb_roomlist_cancel; - pinfo.offline_message = fb_client_offline_message; - pinfo.struct_size = sizeof pinfo; - - opt = purple_account_option_int_new(_("Buddy list sync interval"), - "sync-interval", 5); - opts = g_list_prepend(opts, opt); - - opt = purple_account_option_bool_new(_("Mark messages as read on focus"), - "mark-read", TRUE); - opts = g_list_prepend(opts, opt); - - opt = purple_account_option_bool_new(_("Mark messages as read only when available"), - "mark-read-available", FALSE); - opts = g_list_prepend(opts, opt); - - opt = purple_account_option_bool_new(_("Show self messages"), - "show-self", TRUE); - opts = g_list_prepend(opts, opt); - - opt = purple_account_option_bool_new(_("Show unread messages"), - "show-unread", TRUE); - opts = g_list_prepend(opts, opt); - - opt = purple_account_option_bool_new(_("Open new group chats with " - "incoming messages"), - "group-chat-open", TRUE); - opts = g_list_prepend(opts, opt); - pinfo.protocol_options = g_list_reverse(opts); - - inited = TRUE; - return purple_plugin_register(plugin); + GList *opts = NULL; + PurpleAccountOption *opt; + + static gboolean inited = FALSE; + static PurplePluginInfo info; + static PurplePluginProtocolInfo pinfo; + + (void) fb_protocol; + plugin->info = &info; + + if (G_LIKELY(inited)) { + return purple_plugin_register(plugin); + } + + memset(&info, 0, sizeof info); + memset(&pinfo, 0, sizeof pinfo); + + info.magic = PURPLE_PLUGIN_MAGIC; + info.major_version = PURPLE_MAJOR_VERSION; + info.minor_version = PURPLE_MINOR_VERSION; + info.type = PURPLE_PLUGIN_PROTOCOL; + info.priority = PURPLE_PRIORITY_DEFAULT; + info.id = FB_PROTOCOL_ID; + info.name = "Facebook"; + info.version = PACKAGE_VERSION; + info.summary = N_("Facebook Protocol Plugin"); + info.description = N_("Facebook Protocol Plugin"); + info.homepage = PACKAGE_URL; + info.load = plugin_load; + info.unload = plugin_unload; + info.extra_info = &pinfo; + + pinfo.options = OPT_PROTO_CHAT_TOPIC; + pinfo.list_icon = fb_list_icon; + pinfo.tooltip_text = fb_client_tooltip_text; + pinfo.status_types = fb_status_types; + pinfo.blist_node_menu = fb_client_blist_node_menu; + pinfo.chat_info = fb_chat_info; + pinfo.chat_info_defaults = fb_chat_info_defaults; + pinfo.login = fb_login; + pinfo.close = fb_close; + pinfo.send_im = fb_im_send; + pinfo.send_typing = fb_im_send_typing; + pinfo.set_status = fb_server_set_status; + pinfo.join_chat = fb_chat_join; + pinfo.get_chat_name = fb_chat_get_name; + pinfo.chat_invite = fb_chat_invite; + pinfo.chat_send = fb_chat_send; + pinfo.set_chat_topic = fb_chat_set_topic; + pinfo.roomlist_get_list = fb_roomlist_get_list; + pinfo.roomlist_cancel = fb_roomlist_cancel; + pinfo.offline_message = fb_client_offline_message; + pinfo.struct_size = sizeof pinfo; + + opt = purple_account_option_int_new(_("Buddy list sync interval"), + "sync-interval", 5); + opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Mark messages as read on focus"), + "mark-read", TRUE); + opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Mark messages as read only when available"), + "mark-read-available", FALSE); + opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Show self messages"), + "show-self", TRUE); + opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Show unread messages"), + "show-unread", TRUE); + opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Open new group chats with " + "incoming messages"), + "group-chat-open", TRUE); + opts = g_list_prepend(opts, opt); + pinfo.protocol_options = g_list_reverse(opts); + + inited = TRUE; + return purple_plugin_register(plugin); } From 9d4b92e61936e7ff96cd2a0149d82abf81421fdd Mon Sep 17 00:00:00 2001 From: James Carthew Date: Sun, 7 Apr 2024 16:40:38 +0930 Subject: [PATCH 15/26] Implemented more Bitlbee Facebook API Calls. --- pidgin/libpurple/protocols/facebook/api.c | 76 +++- pidgin/libpurple/protocols/facebook/http.c | 456 ++++++++++----------- pidgin/libpurple/protocols/facebook/http.h | 6 +- 3 files changed, 297 insertions(+), 241 deletions(-) diff --git a/pidgin/libpurple/protocols/facebook/api.c b/pidgin/libpurple/protocols/facebook/api.c index 462129bd..78562e63 100644 --- a/pidgin/libpurple/protocols/facebook/api.c +++ b/pidgin/libpurple/protocols/facebook/api.c @@ -943,7 +943,6 @@ fb_api_http_query(FbApi *api, gint64 query, JsonBuilder *builder, prms = fb_http_params_new(); json = fb_json_bldr_close(builder, JSON_NODE_OBJECT, NULL); - fb_http_params_set_strf(prms, "query_id", "%" G_GINT64_FORMAT, query); fb_http_params_set_str(prms, "query_params", json); g_free(json); @@ -952,8 +951,7 @@ fb_api_http_query(FbApi *api, gint64 query, JsonBuilder *builder, } static void -fb_api_cb_http_bool(PurpleHttpConnection *con, PurpleHttpResponse *res, - gpointer data) +fb_api_cb_http_bool(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { const gchar *hata; FbApi *api = data; @@ -965,8 +963,7 @@ fb_api_cb_http_bool(PurpleHttpConnection *con, PurpleHttpResponse *res, hata = purple_http_response_get_data(res, NULL); if (!purple_strequal(hata, "true")) { - fb_api_error(api, FB_API_ERROR, - _("Failed generic API operation")); + fb_api_error(api, FB_API_ERROR, _("Failed generic API operation")); } } @@ -1274,7 +1271,7 @@ fb_api_event_parse(FbApi *api, FbApiEvent *event, GSList *events, } evtypes[] = { { FB_API_EVENT_TYPE_THREAD_USER_ADDED, - "$.log_message_data.added_participants" + "$.log_message_data.added_participants" }, { FB_API_EVENT_TYPE_THREAD_USER_REMOVED, "$.log_message_data.removed_participants" @@ -2225,11 +2222,58 @@ fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg) fb_http_params_set_strf(prms, "aid", "%" FB_ID_FORMAT, aid); http = fb_api_http_req(api, FB_API_URL_ATTACH, "getAttachment", - "messaging.getAttachment", prms, - fb_api_cb_attach); + "messaging.getAttachment", prms, + fb_api_cb_attach); fb_api_data_set(api, http, msg, (GDestroyNotify) fb_api_message_free); } +static void +fb_api_cb_work_peek(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) +{ + FbApi *api = data; + FbApiPrivate *priv = api->priv; + GError *err = NULL; + JsonNode *root; + gchar *community = NULL; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + /* The work_users[0] explicitly only handles the first user. + * If more than one user is ever needed, this is what you want to change, + * but as far as I know this feature (linked work accounts) is deprecated + * and most users can detach their work accounts from their personal + * accounts by assigning a password to the work account. */ + community = fb_json_node_get_str(root, + "$.data.viewer.work_users[0].community.login_identifier", &err); + + FB_API_ERROR_EMIT(api, err, + g_free(community); + json_node_free(root); + return; + ); + + priv->work_community_id = FB_ID_FROM_STR(community); + + fb_api_auth(api, "X", "X", "personal_to_work_switch"); + + g_free(community); + json_node_free(root); +} + +static PurpleHttpConnection * +fb_api_work_peek(FbApi *api) +{ + FbHttpParams *prms; + + prms = fb_http_params_new(); + fb_http_params_set_int(prms, "doc_id", FB_API_WORK_COMMUNITY_PEEK); + + return fb_api_http_req(api, FB_API_URL_GQL, "WorkCommunityPeekQuery", + "post", prms, fb_api_cb_work_peek); +} + static void fb_api_cb_auth(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) @@ -2264,9 +2308,21 @@ fb_api_cb_auth(PurpleHttpConnection *con, PurpleHttpResponse *res, g_free(priv->token); priv->token = fb_json_values_next_str_dup(values, NULL); - priv->uid = fb_json_values_next_int(values, 0); +// priv->uid = fb_json_values_next_int(values, 0); + + if (priv->is_work) { + priv->uid = FB_ID_FROM_STR(fb_json_values_next_str(values, "0")); + } else { + priv->uid = fb_json_values_next_int(values, 0); + } + + if (priv->need_work_switch) { + fb_api_work_peek(api); + priv->need_work_switch = FALSE; + } else { + g_signal_emit_by_name(api, "auth"); + } - g_signal_emit_by_name(api, "auth"); g_object_unref(values); json_node_free(root); } diff --git a/pidgin/libpurple/protocols/facebook/http.c b/pidgin/libpurple/protocols/facebook/http.c index cd9876cf..f8dc73e0 100644 --- a/pidgin/libpurple/protocols/facebook/http.c +++ b/pidgin/libpurple/protocols/facebook/http.c @@ -27,104 +27,104 @@ struct _FbHttpConns { - GHashTable *cons; - gboolean canceled; + GHashTable *cons; + gboolean canceled; }; GQuark fb_http_error_quark(void) { - static GQuark q = 0; + static GQuark q = 0; - if (G_UNLIKELY(q == 0)) { - q = g_quark_from_static_string("fb-http-error-quark"); - } + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-http-error-quark"); + } - return q; + return q; } FbHttpConns * fb_http_conns_new(void) { - FbHttpConns *cons; + FbHttpConns *cons; - cons = g_new0(FbHttpConns, 1); - cons->cons = g_hash_table_new(g_direct_hash, g_direct_equal); - return cons; + cons = g_new0(FbHttpConns, 1); + cons->cons = g_hash_table_new(g_direct_hash, g_direct_equal); + return cons; } void fb_http_conns_free(FbHttpConns *cons) { - g_return_if_fail(cons != NULL); + g_return_if_fail(cons != NULL); - g_hash_table_destroy(cons->cons); - g_free(cons); + g_hash_table_destroy(cons->cons); + g_free(cons); } void fb_http_conns_cancel_all(FbHttpConns *cons) { - GHashTableIter iter; - gpointer con; + GHashTableIter iter; + gpointer con; - g_return_if_fail(cons != NULL); - g_return_if_fail(!cons->canceled); + g_return_if_fail(cons != NULL); + g_return_if_fail(!cons->canceled); - cons->canceled = TRUE; - g_hash_table_iter_init(&iter, cons->cons); + cons->canceled = TRUE; + g_hash_table_iter_init(&iter, cons->cons); - while (g_hash_table_iter_next(&iter, &con, NULL)) { - g_hash_table_iter_remove(&iter); - purple_http_conn_cancel(con); - } + while (g_hash_table_iter_next(&iter, &con, NULL)) { + g_hash_table_iter_remove(&iter); + purple_http_conn_cancel(con); + } } gboolean fb_http_conns_is_canceled(FbHttpConns *cons) { - g_return_val_if_fail(cons != NULL, TRUE); - return cons->canceled; + g_return_val_if_fail(cons != NULL, TRUE); + return cons->canceled; } void fb_http_conns_add(FbHttpConns *cons, PurpleHttpConnection *con) { - g_return_if_fail(cons != NULL); - g_return_if_fail(!cons->canceled); - g_hash_table_replace(cons->cons, con, con); + g_return_if_fail(cons != NULL); + g_return_if_fail(!cons->canceled); + g_hash_table_replace(cons->cons, con, con); } void fb_http_conns_remove(FbHttpConns *cons, PurpleHttpConnection *con) { - g_return_if_fail(cons != NULL); - g_return_if_fail(!cons->canceled); - g_hash_table_remove(cons->cons, con); + g_return_if_fail(cons != NULL); + g_return_if_fail(!cons->canceled); + g_hash_table_remove(cons->cons, con); } void fb_http_conns_reset(FbHttpConns *cons) { - g_return_if_fail(cons != NULL); - cons->canceled = FALSE; - g_hash_table_remove_all(cons->cons); + g_return_if_fail(cons != NULL); + cons->canceled = FALSE; + g_hash_table_remove_all(cons->cons); } gboolean fb_http_error_chk(PurpleHttpResponse *res, GError **error) { - const gchar *msg; - gint code; + const gchar *msg; + gint code; - if (purple_http_response_is_successful(res)) { - return TRUE; - } + if (purple_http_response_is_successful(res)) { + return TRUE; + } - msg = purple_http_response_get_error(res); - code = purple_http_response_get_code(res); - g_set_error(error, FB_HTTP_ERROR, code, "%s", msg); - return FALSE; + msg = purple_http_response_get_error(res); + code = purple_http_response_get_code(res); + g_set_error(error, FB_HTTP_ERROR, code, "%s", msg); + return FALSE; } FbHttpParams * @@ -136,303 +136,303 @@ fb_http_params_new(void) FbHttpParams * fb_http_params_new_parse(const gchar *data, gboolean isurl) { - const gchar *tail; - gchar *key; - gchar **ps; - gchar *val; - guint i; - FbHttpParams *params; + const gchar *tail; + gchar *key; + gchar **ps; + gchar *val; + guint i; + FbHttpParams *params; - params = fb_http_params_new(); + params = fb_http_params_new(); - if (data == NULL) { - return params; - } + if (data == NULL) { + return params; + } - if (isurl) { - data = strchr(data, '?'); + if (isurl) { + data = strchr(data, '?'); - if (data == NULL) { - return params; - } + if (data == NULL) { + return params; + } - tail = strchr(++data, '#'); + tail = strchr(++data, '#'); - if (tail != NULL) { - data = g_strndup(data, tail - data); - } else { - data = g_strdup(data); - } - } + if (tail != NULL) { + data = g_strndup(data, tail - data); + } else { + data = g_strdup(data); + } + } - ps = g_strsplit(data, "&", 0); + ps = g_strsplit(data, "&", 0); - for (i = 0; ps[i] != NULL; i++) { - key = ps[i]; - val = strchr(ps[i], '='); + for (i = 0; ps[i] != NULL; i++) { + key = ps[i]; + val = strchr(ps[i], '='); - if (val == NULL) { - continue; - } + if (val == NULL) { + continue; + } - *(val++) = 0; - key = g_uri_unescape_string(key, NULL); - val = g_uri_unescape_string(val, NULL); - g_hash_table_replace(params, key, val); - } + *(val++) = 0; + key = g_uri_unescape_string(key, NULL); + val = g_uri_unescape_string(val, NULL); + g_hash_table_replace(params, key, val); + } - if (isurl) { - g_free((gchar *) data); - } + if (isurl) { + g_free((gchar *) data); + } - g_strfreev(ps); - return params; + g_strfreev(ps); + return params; } void fb_http_params_free(FbHttpParams *params) { - g_hash_table_destroy(params); + g_hash_table_destroy(params); } gchar * fb_http_params_close(FbHttpParams *params, const gchar *url) { - GHashTableIter iter; - gpointer key; - gpointer val; - GString *ret; - - g_hash_table_iter_init(&iter, params); - ret = g_string_new(NULL); - - while (g_hash_table_iter_next(&iter, &key, &val)) { - if (val == NULL) { - g_hash_table_iter_remove(&iter); - continue; - } - - if (ret->len > 0) { - g_string_append_c(ret, '&'); - } - - g_string_append_uri_escaped(ret, key, NULL, TRUE); - g_string_append_c(ret, '='); - g_string_append_uri_escaped(ret, val, NULL, TRUE); - } - - if (url != NULL) { - g_string_prepend_c(ret, '?'); - g_string_prepend(ret, url); - } - - fb_http_params_free(params); - return g_string_free(ret, FALSE); + GHashTableIter iter; + gpointer key; + gpointer val; + GString *ret; + + g_hash_table_iter_init(&iter, params); + ret = g_string_new(NULL); + + while (g_hash_table_iter_next(&iter, &key, &val)) { + if (val == NULL) { + g_hash_table_iter_remove(&iter); + continue; + } + + if (ret->len > 0) { + g_string_append_c(ret, '&'); + } + + g_string_append_uri_escaped(ret, key, NULL, TRUE); + g_string_append_c(ret, '='); + g_string_append_uri_escaped(ret, val, NULL, TRUE); + } + + if (url != NULL) { + g_string_prepend_c(ret, '?'); + g_string_prepend(ret, url); + } + + fb_http_params_free(params); + return g_string_free(ret, FALSE); } static const gchar * fb_http_params_get(FbHttpParams *params, const gchar *name, GError **error) { - const gchar *ret; + const gchar *ret; - ret = g_hash_table_lookup(params, name); + ret = g_hash_table_lookup(params, name); - if (ret == NULL) { - g_set_error(error, FB_HTTP_ERROR, FB_HTTP_ERROR_NOMATCH, - _("No matches for %s"), name); - return NULL; - } + if (ret == NULL) { + g_set_error(error, FB_HTTP_ERROR, FB_HTTP_ERROR_NOMATCH, + _("No matches for %s"), name); + return NULL; + } - return ret; + return ret; } gboolean fb_http_params_get_bool(FbHttpParams *params, const gchar *name, GError **error) { - const gchar *val; + const gchar *val; - val = fb_http_params_get(params, name, error); + val = fb_http_params_get(params, name, error); - if (val == NULL) { - return FALSE; - } + if (val == NULL) { + return FALSE; + } - return g_ascii_strcasecmp(val, "TRUE") == 0; + return g_ascii_strcasecmp(val, "TRUE") == 0; } gdouble fb_http_params_get_dbl(FbHttpParams *params, const gchar *name, GError **error) { - const gchar *val; + const gchar *val; - val = fb_http_params_get(params, name, error); + val = fb_http_params_get(params, name, error); - if (val == NULL) { - return 0.0; - } + if (val == NULL) { + return 0.0; + } - return g_ascii_strtod(val, NULL); + return g_ascii_strtod(val, NULL); } gint64 fb_http_params_get_int(FbHttpParams *params, const gchar *name, GError **error) { - const gchar *val; + const gchar *val; - val = fb_http_params_get(params, name, error); + val = fb_http_params_get(params, name, error); - if (val == NULL) { - return 0; - } + if (val == NULL) { + return 0; + } - return g_ascii_strtoll(val, NULL, 10); + return g_ascii_strtoll(val, NULL, 10); } const gchar * fb_http_params_get_str(FbHttpParams *params, const gchar *name, GError **error) { - return fb_http_params_get(params, name, error); + return fb_http_params_get(params, name, error); } gchar * fb_http_params_dup_str(FbHttpParams *params, const gchar *name, GError **error) { - const gchar *str; + const gchar *str; - str = fb_http_params_get(params, name, error); - return g_strdup(str); + str = fb_http_params_get(params, name, error); + return g_strdup(str); } static void fb_http_params_set(FbHttpParams *params, const gchar *name, gchar *value) { - gchar *key; + gchar *key; - key = g_strdup(name); - g_hash_table_replace(params, key, value); + key = g_strdup(name); + g_hash_table_replace(params, key, value); } void fb_http_params_set_bool(FbHttpParams *params, const gchar *name, gboolean value) { - gchar *val; + gchar *val; - val = g_strdup(value ? "true" : "false"); - fb_http_params_set(params, name, val); + val = g_strdup(value ? "true" : "false"); + fb_http_params_set(params, name, val); } void fb_http_params_set_dbl(FbHttpParams *params, const gchar *name, gdouble value) { - gchar *val; + gchar *val; - val = g_strdup_printf("%f", value); - fb_http_params_set(params, name, val); + val = g_strdup_printf("%f", value); + fb_http_params_set(params, name, val); } void fb_http_params_set_int(FbHttpParams *params, const gchar *name, gint64 value) { - gchar *val; + gchar *val; - val = g_strdup_printf("%" G_GINT64_FORMAT, value); - fb_http_params_set(params, name, val); + val = g_strdup_printf("%" G_GINT64_FORMAT, value); + fb_http_params_set(params, name, val); } void fb_http_params_set_str(FbHttpParams *params, const gchar *name, const gchar *value) { - gchar *val; + gchar *val; - val = g_strdup(value); - fb_http_params_set(params, name, val); + val = g_strdup(value); + fb_http_params_set(params, name, val); } void fb_http_params_set_strf(FbHttpParams *params, const gchar *name, const gchar *format, ...) { - gchar *val; - va_list ap; + gchar *val; + va_list ap; - va_start(ap, format); - val = g_strdup_vprintf(format, ap); - va_end(ap); + va_start(ap, format); + val = g_strdup_vprintf(format, ap); + va_end(ap); - fb_http_params_set(params, name, val); + fb_http_params_set(params, name, val); } gboolean fb_http_urlcmp(const gchar *url1, const gchar *url2, gboolean protocol) { - const gchar *str1; - const gchar *str2; - gboolean ret = TRUE; - gint int1; - gint int2; - guint i; - PurpleHttpURL *purl1; - PurpleHttpURL *purl2; - - static const gchar * (*funcs[]) (const PurpleHttpURL *url) = { - /* Always first so it can be skipped */ - purple_http_url_get_protocol, - - purple_http_url_get_fragment, - purple_http_url_get_host, - purple_http_url_get_password, - purple_http_url_get_path, - purple_http_url_get_username - }; - - if ((url1 == NULL) || (url2 == NULL)) { - return url1 == url2; - } - - if (strstr(url1, url2) != NULL || strstr(url2, url1) != NULL) { - return TRUE; - } - - purl1 = purple_http_url_parse(url1); - - if (purl1 == NULL) { - return g_ascii_strcasecmp(url1, url2) == 0; - } - - purl2 = purple_http_url_parse(url2); - - if (purl2 == NULL) { - purple_http_url_free(purl1); - return g_ascii_strcasecmp(url1, url2) == 0; - } - - for (i = protocol ? 0 : 1; i < G_N_ELEMENTS(funcs); i++) { - str1 = funcs[i](purl1); - str2 = funcs[i](purl2); - - if (!purple_strequal(str1, str2)) { - ret = FALSE; - break; - } - } - - if (ret && protocol) { - int1 = purple_http_url_get_port(purl1); - int2 = purple_http_url_get_port(purl2); - - if (int1 != int2) { - ret = FALSE; - } - } - - purple_http_url_free(purl1); - purple_http_url_free(purl2); - return ret; + const gchar *str1; + const gchar *str2; + gboolean ret = TRUE; + gint int1; + gint int2; + guint i; + PurpleHttpURL *purl1; + PurpleHttpURL *purl2; + + static const gchar * (*funcs[]) (const PurpleHttpURL *url) = { + /* Always first so it can be skipped */ + purple_http_url_get_protocol, + + purple_http_url_get_fragment, + purple_http_url_get_host, + purple_http_url_get_password, + purple_http_url_get_path, + purple_http_url_get_username + }; + + if ((url1 == NULL) || (url2 == NULL)) { + return url1 == url2; + } + + if (strstr(url1, url2) != NULL || strstr(url2, url1) != NULL) { + return TRUE; + } + + purl1 = purple_http_url_parse(url1); + + if (purl1 == NULL) { + return g_ascii_strcasecmp(url1, url2) == 0; + } + + purl2 = purple_http_url_parse(url2); + + if (purl2 == NULL) { + purple_http_url_free(purl1); + return g_ascii_strcasecmp(url1, url2) == 0; + } + + for (i = protocol ? 0 : 1; i < G_N_ELEMENTS(funcs); i++) { + str1 = funcs[i](purl1); + str2 = funcs[i](purl2); + + if (!purple_strequal(str1, str2)) { + ret = FALSE; + break; + } + } + + if (ret && protocol) { + int1 = purple_http_url_get_port(purl1); + int2 = purple_http_url_get_port(purl2); + + if (int1 != int2) { + ret = FALSE; + } + } + + purple_http_url_free(purl1); + purple_http_url_free(purl2); + return ret; } diff --git a/pidgin/libpurple/protocols/facebook/http.h b/pidgin/libpurple/protocols/facebook/http.h index 8419ddae..bba52b4c 100644 --- a/pidgin/libpurple/protocols/facebook/http.h +++ b/pidgin/libpurple/protocols/facebook/http.h @@ -65,8 +65,8 @@ typedef GHashTable FbHttpParams; */ typedef enum { - FB_HTTP_ERROR_SUCCESS = 0, - FB_HTTP_ERROR_NOMATCH + FB_HTTP_ERROR_SUCCESS = 0, + FB_HTTP_ERROR_NOMATCH } FbHttpError; /** @@ -303,7 +303,7 @@ fb_http_params_dup_str(FbHttpParams *params, const gchar *name, */ void fb_http_params_set_bool(FbHttpParams *params, const gchar *name, - gboolean value); + gboolean value); /** * fb_http_params_set_dbl: From 33d1285251348c6f6372d9b79ae5eab31ca4514e Mon Sep 17 00:00:00 2001 From: James Carthew Date: Sun, 7 Apr 2024 16:44:05 +0930 Subject: [PATCH 16/26] More Bitlbee Facebook 2FA Auth Code. --- pidgin/libpurple/protocols/facebook/api.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pidgin/libpurple/protocols/facebook/api.c b/pidgin/libpurple/protocols/facebook/api.c index 78562e63..aa3f8130 100644 --- a/pidgin/libpurple/protocols/facebook/api.c +++ b/pidgin/libpurple/protocols/facebook/api.c @@ -2336,6 +2336,25 @@ fb_api_auth(FbApi *api, const gchar *user, const gchar *pass, const gchar *crede prms = fb_http_params_new(); fb_http_params_set_str(prms, "email", user); fb_http_params_set_str(prms, "password", pass); + + if (credentials_type) { + fb_http_params_set_str(prms, "credentials_type", credentials_type); + } + + if (priv->sso_verifier) { + fb_http_params_set_str(prms, "code_verifier", priv->sso_verifier); + g_free(priv->sso_verifier); + priv->sso_verifier = NULL; + } + + if (priv->work_community_id) { + fb_http_params_set_int(prms, "community_id", priv->work_community_id); + } + + if (priv->is_work && priv->token) { + fb_http_params_set_str(prms, "access_token", priv->token); + } + fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login", prms, fb_api_cb_auth); } From 5bca8cecc8a6062970b1493cdd394499921fe696 Mon Sep 17 00:00:00 2001 From: James Carthew Date: Sun, 7 Apr 2024 16:50:39 +0930 Subject: [PATCH 17/26] Implemented fb_api_cb_work_prelogin and fb_api_auth. --- pidgin/libpurple/protocols/facebook/api.c | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/pidgin/libpurple/protocols/facebook/api.c b/pidgin/libpurple/protocols/facebook/api.c index aa3f8130..18b5f260 100644 --- a/pidgin/libpurple/protocols/facebook/api.c +++ b/pidgin/libpurple/protocols/facebook/api.c @@ -2359,6 +2359,70 @@ fb_api_auth(FbApi *api, const gchar *user, const gchar *pass, const gchar *crede prms, fb_api_cb_auth); } +static void +fb_api_cb_work_prelogin(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) +{ + FbApiPreloginData *pata = data; + FbApi *api = pata->api; + FbApiPrivate *priv = api->priv; + GError *err = NULL; + JsonNode *root; + gchar *status; + gchar *user = pata->user; + gchar *pass = pata->pass; + + g_free(pata); + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + status = fb_json_node_get_str(root, "$.status", &err); + + FB_API_ERROR_EMIT(api, err, + json_node_free(root); + return; + ); + + if (g_strcmp0(status, "can_login_password") == 0) { + fb_api_auth(api, user, pass, "work_account_password"); + + } else if (g_strcmp0(status, "can_login_via_linked_account") == 0) { + fb_api_auth(api, user, pass, "personal_account_password_with_work_username"); + priv->need_work_switch = TRUE; + + } else if (g_strcmp0(status, "can_login_sso") == 0) { + g_signal_emit_by_name(api, "work-sso-login"); + + } else if (g_strcmp0(status, "cannot_login") == 0) { + char *reason = fb_json_node_get_str(root, "$.cannot_login_reason", NULL); + + if (g_strcmp0(reason, "non_business_email") == 0) { + fb_api_error(api, FB_API_ERROR_AUTH, + "Cannot login with non-business email. " + "Change the 'username' setting or disable 'work'"); + } else { + char *title = fb_json_node_get_str(root, "$.error_title", NULL); + char *body = fb_json_node_get_str(root, "$.error_body", NULL); + + fb_api_error(api, FB_API_ERROR_AUTH, + "Work prelogin failed (%s - %s)", title, body); + + g_free(title); + g_free(body); + } + + g_free(reason); + + } else if (g_strcmp0(status, "can_self_invite") == 0) { + fb_api_error(api, FB_API_ERROR_AUTH, "Unknown email. " + "Change the 'username' setting or disable 'work'"); + } + + g_free(status); + json_node_free(root); +} + static gchar * fb_api_user_icon_checksum(gchar *icon) { From dc44f9cd0105145560ae6588ff3f6b556595c9c9 Mon Sep 17 00:00:00 2001 From: James Carthew Date: Sun, 7 Apr 2024 17:49:11 +0930 Subject: [PATCH 18/26] More API Updates. --- pidgin/libpurple/protocols/facebook/api.c | 86 +++++++++++++++++++++- pidgin/libpurple/protocols/facebook/http.h | 2 + pidgin/libpurple/protocols/facebook/util.c | 43 +++++++++++ pidgin/libpurple/protocols/facebook/util.h | 26 +++++++ 4 files changed, 156 insertions(+), 1 deletion(-) diff --git a/pidgin/libpurple/protocols/facebook/api.c b/pidgin/libpurple/protocols/facebook/api.c index 18b5f260..902365eb 100644 --- a/pidgin/libpurple/protocols/facebook/api.c +++ b/pidgin/libpurple/protocols/facebook/api.c @@ -2423,6 +2423,90 @@ fb_api_cb_work_prelogin(PurpleHttpConnection *con, PurpleHttpResponse *res, gpoi json_node_free(root); } +void +fb_api_work_login(FbApi *api, gchar *user, gchar *pass) +{ + FbApiPrivate *priv = api->priv; + PurpleHttpConnection *con; + FbHttpParams *prms, *hdrs; + FbApiPreloginData *pata = g_new0(FbApiPreloginData, 1); + + pata->api = api; + pata->user = user; + pata->pass = pass; + + priv->is_work = TRUE; + +/* //THIS NEEDS REFACTOR + con = purple_http_request_new(FB_API_URL_WORK_PRELOGIN); + con = purple_http_request_new(priv->cons, FB_API_URL_WORK_PRELOGIN, TRUE, fb_api_cb_work_prelogin, pata); + + hdrs = fb_http_request_get_headers(con); + fb_http_params_set_str(hdrs, "Authorization", "OAuth null"); + + prms = fb_http_request_get_params(req); + fb_http_params_set_str(prms, "email", user); + fb_http_params_set_str(prms, "access_token", + FB_WORK_API_KEY "|" FB_WORK_API_SECRET); + + fb_http_request_send(req); //no equivalent for libpurple. +*/ +} + +gchar * +fb_api_work_gen_sso_url(FbApi *api, const gchar *user) +{ + FbApiPrivate *priv = api->priv; + gchar *challenge, *verifier, *req_id, *email; + gchar *ret; + + fb_util_gen_sso_verifier(&challenge, &verifier, &req_id); + + email = g_uri_escape_string(user, NULL, FALSE); + + ret = g_strdup_printf(FB_API_SSO_URL, req_id, challenge, email); + + g_free(req_id); + g_free(challenge); + g_free(email); + + g_free(priv->sso_verifier); + priv->sso_verifier = verifier; + + return ret; +} + +void +fb_api_work_got_nonce(FbApi *api, const gchar *url) +{ + gchar **split; + gchar *uid = NULL; + gchar *nonce = NULL; + int i; + + if (!g_str_has_prefix(url, "fb-workchat-sso://sso/?")) { + return; + } + + split = g_strsplit(strchr(url, '?'), "&", -1); + + for (i = 0; split[i]; i++) { + gchar *eq = strchr(split[i], '='); + + if (g_str_has_prefix(split[i], "uid=")) { + uid = g_strstrip(eq + 1); + } else if (g_str_has_prefix(split[i], "nonce=")) { + nonce = g_strstrip(eq + 1); + } + } + + if (uid && nonce) { + fb_api_auth(api, uid, nonce, "work_sso_nonce"); + } + + g_strfreev(split); +} + static gchar * fb_api_user_icon_checksum(gchar *icon) { @@ -3461,7 +3545,7 @@ fb_api_thread_topic(FbApi *api, FbId tid, const gchar *topic) fb_http_params_set_int(prms, "tid", tid); fb_api_http_req(api, FB_API_URL_TOPIC, "setThreadName", "messaging.setthreadname", prms, - fb_api_cb_http_bool); + fb_api_cb_http_bool); } static void diff --git a/pidgin/libpurple/protocols/facebook/http.h b/pidgin/libpurple/protocols/facebook/http.h index bba52b4c..5cd9dbee 100644 --- a/pidgin/libpurple/protocols/facebook/http.h +++ b/pidgin/libpurple/protocols/facebook/http.h @@ -59,6 +59,7 @@ typedef GHashTable FbHttpParams; /** * FbHttpError: * @FB_HTTP_ERROR_SUCCESS: There is no error. + * @FB_HTTP_ERROR_INIT: The request failed to initialize. * @FB_HTTP_ERROR_NOMATCH: The name does not match anything. * * The error codes for the #FB_HTTP_ERROR domain. @@ -66,6 +67,7 @@ typedef GHashTable FbHttpParams; typedef enum { FB_HTTP_ERROR_SUCCESS = 0, + FB_HTTP_ERROR_INIT, FB_HTTP_ERROR_NOMATCH } FbHttpError; diff --git a/pidgin/libpurple/protocols/facebook/util.c b/pidgin/libpurple/protocols/facebook/util.c index 72890a5a..f7cc46e9 100644 --- a/pidgin/libpurple/protocols/facebook/util.c +++ b/pidgin/libpurple/protocols/facebook/util.c @@ -564,3 +564,46 @@ fb_util_zlib_inflate(const GByteArray *bytes, GError **error) g_object_unref(conv); return ret; } + +gchar * +fb_util_urlsafe_base64_encode(const guchar *data, gsize len) +{ + gchar *out = g_base64_encode(data, len); + gchar *c; + + for (c = out; *c; c++) { + if (*c == '+') { + *c = '-'; + } else if (*c == '/') { + *c = '_'; + } else if (*c == '=') { + *c = '\0'; + break; + } + } + + return out; +} + +void +fb_util_gen_sso_verifier(gchar **challenge, gchar **verifier, gchar **req_id) +{ + guint8 buf[32]; + GChecksum *gc; + gsize digest_len = sizeof buf; + +// random_bytes(buf, sizeof buf); + + *verifier = fb_util_urlsafe_base64_encode(buf, sizeof buf); + + gc = g_checksum_new(G_CHECKSUM_SHA256); + g_checksum_update(gc, (guchar *) *verifier, -1); + g_checksum_get_digest(gc, buf, &digest_len); + g_checksum_free(gc); + + *challenge = fb_util_urlsafe_base64_encode(buf, sizeof buf); + +// random_bytes(buf, 3); + + *req_id = fb_util_urlsafe_base64_encode(buf, 3); +} diff --git a/pidgin/libpurple/protocols/facebook/util.h b/pidgin/libpurple/protocols/facebook/util.h index 2a1d5ebf..5f8ecaa1 100644 --- a/pidgin/libpurple/protocols/facebook/util.h +++ b/pidgin/libpurple/protocols/facebook/util.h @@ -347,4 +347,30 @@ fb_util_zlib_deflate(const GByteArray *bytes, GError **error); GByteArray * fb_util_zlib_inflate(const GByteArray *bytes, GError **error); +/** + * fb_util_urlsafe_base64_encode: + * @data: the binary data to encode. + * @len: the length of data + * + * Wrapper around g_base64_encode() which substitutes '-' instead of '+' + * and '_' instead of '/' and removes the padding + * + * Returns: A newly allocated string. + */ + +gchar * +fb_util_urlsafe_base64_encode(const guchar *data, gsize len); + +/** + * fb_util_gen_sso_verifier: + * @challenge: base64 of sha256 of verifier + * @verifier: base64 of random data + * @req_id: base64 of random data + * + * Generates the challenge/response parameters used for the workchat SSO auth. + * All parameters are output parameters. + */ +void +fb_util_gen_sso_verifier(gchar **challenge, gchar **verifier, gchar **req_id); + #endif /* _FACEBOOK_UTIL_H_ */ From 423d65ae08b20548ca3f4c92e23845a6ff584a96 Mon Sep 17 00:00:00 2001 From: James Carthew Date: Sun, 7 Apr 2024 21:21:41 +0930 Subject: [PATCH 19/26] Added an update to meson.build resetting the contact URL to dequis' repository. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 85ab4121..80a4c191 100644 --- a/meson.build +++ b/meson.build @@ -11,7 +11,7 @@ project('purple-facebook', 'c') compiler_options = [] compiler_options += ['-DPACKAGE_VERSION="0.9.7"'] -compiler_options += ['-DPACKAGE_URL="https://github.com/DMJC/purple-facebook"'] +compiler_options += ['-DPACKAGE_URL="https://github.com/dequis/purple-facebook/"'] #define('PACKAGE_VERSION', '"0.9.61"') #define('PACKAGE_URL', '"https://github.com/DMJC/purple-facebook"') From e108c27f12e583820057d5501a18c18a3092e28f Mon Sep 17 00:00:00 2001 From: DMJC Date: Sun, 15 Sep 2024 22:44:36 +0930 Subject: [PATCH 20/26] Implemented 2FA SSO. --- pidgin/libpurple/protocols/facebook/api.c | 223 ++++++++++++++++++ pidgin/libpurple/protocols/facebook/data.c | 5 +- .../libpurple/protocols/facebook/facebook.c | 61 +++++ .../libpurple/protocols/facebook/facebook.h | 1 + 4 files changed, 289 insertions(+), 1 deletion(-) diff --git a/pidgin/libpurple/protocols/facebook/api.c b/pidgin/libpurple/protocols/facebook/api.c index 902365eb..a2190c62 100644 --- a/pidgin/libpurple/protocols/facebook/api.c +++ b/pidgin/libpurple/protocols/facebook/api.c @@ -47,6 +47,9 @@ enum PROP_UID, PROP_TWEAK, PROP_WORK, + PROP_MACHINE_ID, + PROP_LOGIN_FIRST_FACTOR, + PROP_TWOFACTOR_CODE, PROP_N }; @@ -77,6 +80,9 @@ struct _FbApiPrivate gboolean need_work_switch; gchar *sso_verifier; FbId work_community_id; + gchar *machine_id; + gchar *login_first_factor; + gchar *twofactor_code; }; struct _FbApiData @@ -166,6 +172,18 @@ fb_api_set_property(GObject *obj, guint prop, const GValue *val, case PROP_WORK: priv->is_work = g_value_get_boolean(val); break; + case PROP_MACHINE_ID: + g_free(priv->machine_id); + priv->machine_id = g_value_dup_string(val); + break; + case PROP_LOGIN_FIRST_FACTOR: + g_free(priv->login_first_factor); + priv->login_first_factor = g_value_dup_string(val); + break; + case PROP_TWOFACTOR_CODE: + g_free(priv->twofactor_code); + priv->twofactor_code = g_value_dup_string(val); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); @@ -203,6 +221,15 @@ fb_api_get_property(GObject *obj, guint prop, GValue *val, GParamSpec *pspec) case PROP_WORK: g_value_set_boolean(val, priv->is_work); break; + case PROP_MACHINE_ID: + g_value_set_string(val, priv->machine_id); + break; + case PROP_LOGIN_FIRST_FACTOR: + g_value_set_string(val, priv->login_first_factor); + break; + case PROP_TWOFACTOR_CODE: + g_value_set_string(val, priv->twofactor_code); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); @@ -239,6 +266,11 @@ fb_api_dispose(GObject *obj) g_free(priv->stoken); g_free(priv->token); g_free(priv->contacts_delta); + //Might need another line here + //g_free(priv->sso_verifier); + g_free(priv->machine_id); + g_free(priv->login_first_factor); + g_free(priv->twofactor_code); } static void @@ -348,6 +380,50 @@ fb_api_class_init(FbApiClass *klass) "", FALSE, G_PARAM_READWRITE); + + + /** + * FbApi:machine_id: + * + * The machine id we ask facebook to generate for us. + * Saved automatically for persistence. + * + * NOT TO BE CONFUSED WITH mid! + */ + props[PROP_MACHINE_ID] = g_param_spec_string( + "machine_id", + "Machine Id", + "Machine Id generated by facebook", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:login_first_factor: + * + * The first factor challenge code fo 2FA. + * Saved automatically for persistence. + */ + props[PROP_LOGIN_FIRST_FACTOR] = g_param_spec_string( + "login_first_factor", + "Login First Factor", + "Login First Factor challenge code for 2FA", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:twofactor_code: + * + * The 2FA code the user receives via external means. + * User needs to set this manually to the account. + */ + props[PROP_TWOFACTOR_CODE] = g_param_spec_string( + "twofactor_code", + "Twofactor Code", + "Twofactor Code externally received, for 2FA", + NULL, + G_PARAM_READWRITE); + + g_object_class_install_properties(gklass, PROP_N, props); /** @@ -366,6 +442,23 @@ fb_api_class_init(FbApiClass *klass) G_TYPE_NONE, 0); + /** + * FbApi::twofactor-code-prompt: + * @api: The #FbApi. + * + * Emitted when we want to instruct the user about how to input + * new twofactor code. + */ + + g_signal_new("twofactor-code-prompt", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + /** * FbApi::connect: * @api: The #FbApi. @@ -662,6 +755,89 @@ fb_api_data_take(FbApi *api, gconstpointer handle) return data; } +static void +fb_api_json_update_from_error_data(FbApi *api, gconstpointer data, gssize size, JsonNode **node) +{ + gchar *str; + FbApiPrivate *priv; + gboolean found_data = FALSE; + GError *err = NULL; + gint64 code; + gint64 uid; + JsonNode *root; + gchar *error_data; + JsonNode *error_data_root; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + if (G_UNLIKELY(size == 0)) { + return; + } + root = fb_json_node_new(data, size, &err); + if (err != NULL) { + g_clear_error(&err); + return; + } + // We handle all error stuff for info, not just 406 + + error_data = fb_json_node_get_str(root, "$.error_data", &err); + if (err != NULL) { + // No error data. This is ok. + json_node_free(root); + g_clear_error(&err); + return; + } + + error_data_root = fb_json_node_new(error_data, -1, &err); + if (err != NULL) { + g_free(error_data); + json_node_free(root); + return; + } + + + // Finally, parse individual datas + // uid, special case + uid = fb_json_node_get_int(error_data_root, "$.uid", &err); + if (err != NULL) { g_clear_error(&err) ; } else { + if (uid != priv->uid) { + priv->uid = uid; + found_data = TRUE; + } + } + // machine_id + str = fb_json_node_get_str(error_data_root, "$.machine_id", &err); + if (err != NULL) { g_clear_error(&err) ; } else { + if (g_strcmp0(str, priv->machine_id) != 0) { + g_free(priv->machine_id); + priv->machine_id = g_strdup(str); + found_data = TRUE; + } + g_free(str); + } + // login_first_factor + str = fb_json_node_get_str(error_data_root, "$.login_first_factor", &err); + if (err != NULL) { g_clear_error(&err) ; } else { + if (g_strcmp0(str, priv->login_first_factor) != 0) { + g_free(priv->login_first_factor); + priv->login_first_factor = g_strdup(str); + found_data = TRUE; + } + g_free(str); + } + + json_node_free(error_data_root); + g_free(error_data); + json_node_free(root); + + // If the data changed, with high probability we need new code and invalidate old + if (found_data) { + g_free(priv->twofactor_code); + priv->twofactor_code = NULL; + } +} + static gboolean fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node) { @@ -722,6 +898,13 @@ fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node) g_free(priv->token); priv->token = NULL; + + g_free(priv->twofactor_code); + priv->twofactor_code = NULL; + } + /* 406 is used for MFA Prompt*/ + if (code == 406) { + g_signal_emit_by_name(api, "twofactor-code-prompt"); } /* 509 is used for "invalid attachment id" */ @@ -818,6 +1001,10 @@ fb_api_http_chk(FbApi *api, PurpleHttpConnection *con, PurpleHttpResponse *res, FB_API_ERROR_EMIT(api, err, return FALSE); } + // Scan possible error data for important auth state info changes + fb_api_json_update_from_error_data(api, data, size, root); + + // Actual, propagable errors still handled here: if (!fb_api_json_chk(api, data, size, root)) { if (G_UNLIKELY(err != NULL)) { g_error_free(err); @@ -2355,6 +2542,42 @@ fb_api_auth(FbApi *api, const gchar *user, const gchar *pass, const gchar *crede fb_http_params_set_str(prms, "access_token", priv->token); } + if (!(priv->machine_id) || strlen(priv->machine_id) == 0) { + fb_http_params_set_str(prms, "generate_machine_id", "1"); + } else { + fb_http_params_set_str(prms, "machine_id", priv->machine_id); + } + + if (priv->uid && + priv->machine_id && strlen(priv->machine_id) > 0 && + priv->login_first_factor && strlen(priv->login_first_factor) > 0 && + priv->twofactor_code && strlen(priv->twofactor_code) > 0) { + + // Everything ready for the whole 2fa auth set + + // uid, set_int is fine here + fb_http_params_set_int(prms, "uid", priv->uid); + + // device_id comes from somewhere else + + // credentials_type magic + fb_http_params_set_str(prms, "credentials_type", "two_factor"); + + // first_factor, yes cleverly wants it with different name than gives + fb_http_params_set_str(prms, "first_factor", priv->login_first_factor); + + // twofactor_code + fb_http_params_set_str(prms, "twofactor_code", priv->twofactor_code); + + // password actually now same as twofactor_code + fb_http_params_set_str(prms, "password", priv->twofactor_code); + + // userid , same as uid. needs to be here for 2FA to work. + fb_http_params_set_int(prms, "userid", priv->uid); + + // machine_id already there (otherwise we would not be in this loop) + } + fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login", prms, fb_api_cb_auth); } diff --git a/pidgin/libpurple/protocols/facebook/data.c b/pidgin/libpurple/protocols/facebook/data.c index 6ade3bf5..9662b56d 100644 --- a/pidgin/libpurple/protocols/facebook/data.c +++ b/pidgin/libpurple/protocols/facebook/data.c @@ -56,7 +56,10 @@ static const gchar *fb_props_strs[] = { "cid", "did", "stoken", - "token" + "token", + "machine_id", + "login_first_factor", + "twofactor_code" }; G_DEFINE_TYPE_WITH_CODE(FbData, fb_data, G_TYPE_OBJECT, G_ADD_PRIVATE(FbData)); diff --git a/pidgin/libpurple/protocols/facebook/facebook.c b/pidgin/libpurple/protocols/facebook/facebook.c index fc99c2b4..fea7c02f 100644 --- a/pidgin/libpurple/protocols/facebook/facebook.c +++ b/pidgin/libpurple/protocols/facebook/facebook.c @@ -117,9 +117,17 @@ static void fb_cb_api_auth(FbApi *api, gpointer data) { FbData *fata = data; + PurpleBuddy *buddy; + PurpleAccount *acct; PurpleConnection *gc; gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + buddy = purple_find_buddy(acct, FB_SSO_HANDLE); + + if (buddy) { + purple_blist_remove_buddy(buddy); + } purple_connection_update_progress(gc, _("Fetching contacts"), 2, 4); fb_data_save(fata); @@ -169,6 +177,17 @@ fb_cb_api_contact(FbApi *api, FbApiUser *user, gpointer data) } } +static void +fb_cb_api_twofactor_code_prompt(FbApi *api, gpointer data) +{ + FbData *fata = data; +//rewrite for Purple +/* struct im_connection *ic; + + ic = fb_data_get_connection(fata); + imcb_log(ic, "If you receive new 2FA code, do like this: acc facebook set twofactor_code ");*/ +} + static gboolean fb_cb_sync_contacts(gpointer data) { @@ -827,6 +846,40 @@ fb_cb_api_typing(FbApi *api, FbApiTyping *typg, gpointer data) } } +static void +fb_cb_api_work_sso_login(FbApi *api, gpointer data) +{ + PurpleConnection *gc; + PurpleAccount *acct; + gchar *url; + + // Get the PurpleConnection from the FbData structure + gc = purple_account_get_connection(acct); + + // Get the PurpleAccount associated with the connection + acct = purple_connection_get_account(gc); + + // Generate the SSO URL using the user's account info + url = fb_api_work_gen_sso_url(api, purple_account_get_username(acct)); + + // Add a "buddy" (handle) for interaction (this might be optional) + purple_blist_add_buddy(purple_buddy_new(acct, FB_SSO_HANDLE, NULL), NULL, NULL, NULL); + + // Send the first message to the buddy with authentication instructions + serv_got_im(gc, FB_SSO_HANDLE, "Open this URL in your browser to authenticate:", PURPLE_MESSAGE_SYSTEM, time(NULL)); + serv_got_im(gc, FB_SSO_HANDLE, url, PURPLE_MESSAGE_SYSTEM, time(NULL)); + serv_got_im(gc, FB_SSO_HANDLE, + "Respond to this message with the URL starting with 'fb-workchat-sso://' that it attempts to redirect to.", + PURPLE_MESSAGE_SYSTEM, time(NULL)); + serv_got_im(gc, FB_SSO_HANDLE, + "If your browser says 'Address not understood' (like firefox), copy it from the address bar. " + "Otherwise you might have to right-click -> view source in the last page and find it there. Good luck!", + PURPLE_MESSAGE_SYSTEM, time(NULL)); + + // Free the generated URL after use + g_free(url); +} + static void fb_mark_read(FbData *fata, FbId id, gboolean thread) { @@ -1000,6 +1053,14 @@ fb_login(PurpleAccount *acct) convh = purple_conversations_get_handle(); purple_connection_set_protocol_data(gc, fata); + g_signal_connect(api, + "work-sso-login", + G_CALLBACK(fb_cb_api_work_sso_login), + fata); + g_signal_connect(api, + "twofactor-code-prompt", + G_CALLBACK(fb_cb_api_twofactor_code_prompt), + fata); g_signal_connect(api, "auth", G_CALLBACK(fb_cb_api_auth), diff --git a/pidgin/libpurple/protocols/facebook/facebook.h b/pidgin/libpurple/protocols/facebook/facebook.h index aa8b3c34..cc6b132e 100644 --- a/pidgin/libpurple/protocols/facebook/facebook.h +++ b/pidgin/libpurple/protocols/facebook/facebook.h @@ -30,5 +30,6 @@ * The Facebook protocol identifier. */ #define FB_PROTOCOL_ID "prpl-facebook" +#define FB_SSO_HANDLE "facebook_sso_auth" #endif /* _FACEBOOK_H_ */ From 23e445100ab6f654f63efffa4a2fce148c5a0ddb Mon Sep 17 00:00:00 2001 From: DMJC Date: Mon, 16 Sep 2024 23:03:25 +0930 Subject: [PATCH 21/26] Update Readme. --- README | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README b/README index 25c5aee3..36220c71 100644 --- a/README +++ b/README @@ -9,3 +9,11 @@ This has been forked from https://github.com/dequis/purple-facebook/ - Thanks De Build Instructions: git clone the repository, run meson setup build, cd into build directory and type ninja; ninja install to install to plugins folder. More information: https://github.com/dequis/purple-facebook/wiki + +Features: +- Chat is implemented +- Group Chat Works +- MFA/2FA Works + +TODO: +- Implement End to End encryption. I would like some assistance with this, code/information contributions are welcome. From cab3fe47e136cf2a58fc909f69290b4e90c8b978 Mon Sep 17 00:00:00 2001 From: DMJC Date: Mon, 16 Sep 2024 23:10:12 +0930 Subject: [PATCH 22/26] Updated Debian Packaging. --- debian/changelog | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index cab1350f..ee9917de 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,6 @@ -libpurple-facebook (0.9.7) UNRELEASED; urgency=medium +libpurple-facebook (0.9.8) UNRELEASED; urgency=medium + * MFA/2FA support added. * Initial debian support. - -- jgeboski Thu, 01 Jan 1970 00:00:00 +0000 + -- jcarthew Mon, 16 Sep 2024 23:05:00 +0000 From 3edd65a73e5514ebc9af7bc83d058ffce725afb2 Mon Sep 17 00:00:00 2001 From: DMJC Date: Tue, 17 Sep 2024 07:13:15 +0930 Subject: [PATCH 23/26] Updated output filename for drop in compatibility. --- meson.build | 2 +- pidgin/libpurple/.deps/libfacebook_la-http.Plo | 1 - pidgin/libpurple/.deps/libfacebook_la-purple-socket.Plo | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 pidgin/libpurple/.deps/libfacebook_la-http.Plo delete mode 100644 pidgin/libpurple/.deps/libfacebook_la-purple-socket.Plo diff --git a/meson.build b/meson.build index 80a4c191..29c13cff 100644 --- a/meson.build +++ b/meson.build @@ -87,4 +87,4 @@ endif # Define library #plugin_library = shared_library('purple-facebook-plugin', source_files, dependencies : [glib_dep, json_dep, purple_dep, zlib_dep], c_args: compiler_options) -plugin_library = shared_library('purple-facebook-plugin', source_files, dependencies : [glib_dep, json_dep, purple_dep, zlib_dep], c_args: compiler_options, install: true, install_dir: purple_plugindir) +plugin_library = shared_library('facebook', source_files, dependencies : [glib_dep, json_dep, purple_dep, zlib_dep], c_args: compiler_options, install: true, install_dir: purple_plugindir) diff --git a/pidgin/libpurple/.deps/libfacebook_la-http.Plo b/pidgin/libpurple/.deps/libfacebook_la-http.Plo deleted file mode 100644 index 9ce06a81..00000000 --- a/pidgin/libpurple/.deps/libfacebook_la-http.Plo +++ /dev/null @@ -1 +0,0 @@ -# dummy diff --git a/pidgin/libpurple/.deps/libfacebook_la-purple-socket.Plo b/pidgin/libpurple/.deps/libfacebook_la-purple-socket.Plo deleted file mode 100644 index 9ce06a81..00000000 --- a/pidgin/libpurple/.deps/libfacebook_la-purple-socket.Plo +++ /dev/null @@ -1 +0,0 @@ -# dummy From e974b9a937a17048e9fc4fedf1991e2847fd8017 Mon Sep 17 00:00:00 2001 From: DMJC Date: Tue, 17 Sep 2024 07:16:43 +0930 Subject: [PATCH 24/26] updated meson.build --- meson.build | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/meson.build b/meson.build index 29c13cff..72048c42 100644 --- a/meson.build +++ b/meson.build @@ -1,25 +1,9 @@ project('purple-facebook', 'c') -# Define package version -#PACKAGE_VERSION = '0.9.61' # Adjust the version number as needed -#PACKAGE_URL = 'https://github.com/DMJC/purple-facebook' - -# Configure the package -#pkgconf = configuration_data() -#pkgconf.set('PACKAGE_VERSION', PACKAGE_VERSION) -#pkgconf.set('PACKAGE_URL', PACKAGE_URL) - compiler_options = [] -compiler_options += ['-DPACKAGE_VERSION="0.9.7"'] +compiler_options += ['-DPACKAGE_VERSION="0.9.8"'] compiler_options += ['-DPACKAGE_URL="https://github.com/dequis/purple-facebook/"'] -#define('PACKAGE_VERSION', '"0.9.61"') -#define('PACKAGE_URL', '"https://github.com/DMJC/purple-facebook"') - -# Write the configuration to a file -#configure_file(output : 'config.h', -# configuration : pkgconf) - # Add compiler flags if warnings are enabled if get_option('warnings') add_project_arguments('-Wall', '-Wextra', '-Waggregate-return', '-Wdeclaration-after-statement', '-Wfloat-equal', '-Wformat', '-Winit-self', '-Wmissing-declarations', '-Wmissing-prototypes', '-Wno-unused-parameter', '-Wpointer-arith', language : 'c') @@ -85,6 +69,4 @@ else endif # Define library -#plugin_library = shared_library('purple-facebook-plugin', source_files, dependencies : [glib_dep, json_dep, purple_dep, zlib_dep], c_args: compiler_options) - plugin_library = shared_library('facebook', source_files, dependencies : [glib_dep, json_dep, purple_dep, zlib_dep], c_args: compiler_options, install: true, install_dir: purple_plugindir) From 96036dbd8f92ac41c3557951c01784c3c0bb868c Mon Sep 17 00:00:00 2001 From: DMJC Date: Tue, 17 Sep 2024 19:25:49 +0930 Subject: [PATCH 25/26] Updated gmemdup for glibc on old Ubuntu. --- pidgin/libpurple/http.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pidgin/libpurple/http.c b/pidgin/libpurple/http.c index 122b2a07..18599155 100644 --- a/pidgin/libpurple/http.c +++ b/pidgin/libpurple/http.c @@ -2664,7 +2664,11 @@ void purple_http_request_set_contents(PurpleHttpRequest *request, if (length == -1) length = strlen(contents); +#if GLIB_CHECK_VERSION(2, 68, 0) + request->contents = g_memdup(contents, length); +#else request->contents = g_memdup2(contents, length); +#endif request->contents_length = length; } From 06a5facd4d88e3572d441223dca710a36ef8fe01 Mon Sep 17 00:00:00 2001 From: DMJC Date: Tue, 17 Sep 2024 19:32:41 +0930 Subject: [PATCH 26/26] GLIB Compatibility. --- pidgin/libpurple/http.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pidgin/libpurple/http.c b/pidgin/libpurple/http.c index 18599155..2194dcbd 100644 --- a/pidgin/libpurple/http.c +++ b/pidgin/libpurple/http.c @@ -2665,9 +2665,9 @@ void purple_http_request_set_contents(PurpleHttpRequest *request, if (length == -1) length = strlen(contents); #if GLIB_CHECK_VERSION(2, 68, 0) - request->contents = g_memdup(contents, length); -#else request->contents = g_memdup2(contents, length); +#else + request->contents = g_memdup(contents, length); #endif request->contents_length = length; }