pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Route GLib debug logging directly to the Finch debug window
2021-10-18, Elliott Sales de Andrade
1896a80ff8e3
Route GLib debug logging directly to the Finch debug window
Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.
Testing Done:
Open the Debug window an see some `GLib-*` outputs.
Reviewed at https://reviews.imfreedom.org/r/1057/
/*
* purple - Handling of XEP-0047: In-Band Bytestreams.
*
* 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
<math.h>
#include
<purple.h>
#include
"ibb.h"
#define JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE 4096
static
GHashTable
*
jabber_ibb_sessions
=
NULL
;
static
GList
*
open_handlers
=
NULL
;
JabberIBBSession
*
jabber_ibb_session_create
(
JabberStream
*
js
,
const
gchar
*
sid
,
const
gchar
*
who
,
gpointer
user_data
)
{
JabberIBBSession
*
sess
=
g_new0
(
JabberIBBSession
,
1
);
sess
->
js
=
js
;
if
(
sid
)
{
sess
->
sid
=
g_strdup
(
sid
);
}
else
{
sess
->
sid
=
jabber_get_next_id
(
js
);
}
sess
->
who
=
g_strdup
(
who
);
sess
->
block_size
=
JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE
;
sess
->
state
=
JABBER_IBB_SESSION_NOT_OPENED
;
sess
->
user_data
=
user_data
;
g_hash_table_insert
(
jabber_ibb_sessions
,
sess
->
sid
,
sess
);
return
sess
;
}
JabberIBBSession
*
jabber_ibb_session_create_from_xmlnode
(
JabberStream
*
js
,
const
char
*
from
,
const
char
*
id
,
PurpleXmlNode
*
open
,
gpointer
user_data
)
{
JabberIBBSession
*
sess
=
NULL
;
const
gchar
*
sid
=
purple_xmlnode_get_attrib
(
open
,
"sid"
);
const
gchar
*
block_size
=
purple_xmlnode_get_attrib
(
open
,
"block-size"
);
if
(
!
open
)
{
return
NULL
;
}
if
(
!
sid
||
!
block_size
)
{
purple_debug_error
(
"jabber"
,
"IBB session open tag requires sid and block-size attributes
\n
"
);
return
NULL
;
}
sess
=
jabber_ibb_session_create
(
js
,
sid
,
from
,
user_data
);
sess
->
id
=
g_strdup
(
id
);
sess
->
block_size
=
atoi
(
block_size
);
/* if we create a session from an incoming <open/> request, it means the
session is immediately open... */
sess
->
state
=
JABBER_IBB_SESSION_OPENED
;
return
sess
;
}
void
jabber_ibb_session_destroy
(
JabberIBBSession
*
sess
)
{
purple_debug_info
(
"jabber"
,
"IBB: destroying session %p %s
\n
"
,
sess
,
sess
->
sid
);
if
(
jabber_ibb_session_get_state
(
sess
)
==
JABBER_IBB_SESSION_OPENED
)
{
jabber_ibb_session_close
(
sess
);
}
if
(
sess
->
last_iq_id
)
{
purple_debug_info
(
"jabber"
,
"IBB: removing callback for <iq/> %s
\n
"
,
sess
->
last_iq_id
);
jabber_iq_remove_callback_by_id
(
jabber_ibb_session_get_js
(
sess
),
sess
->
last_iq_id
);
g_free
(
sess
->
last_iq_id
);
sess
->
last_iq_id
=
NULL
;
}
g_hash_table_remove
(
jabber_ibb_sessions
,
sess
->
sid
);
g_free
(
sess
->
id
);
g_free
(
sess
->
sid
);
g_free
(
sess
->
who
);
g_free
(
sess
);
}
const
gchar
*
jabber_ibb_session_get_sid
(
const
JabberIBBSession
*
sess
)
{
return
sess
->
sid
;
}
JabberStream
*
jabber_ibb_session_get_js
(
JabberIBBSession
*
sess
)
{
return
sess
->
js
;
}
const
gchar
*
jabber_ibb_session_get_who
(
const
JabberIBBSession
*
sess
)
{
return
sess
->
who
;
}
guint16
jabber_ibb_session_get_send_seq
(
const
JabberIBBSession
*
sess
)
{
return
sess
->
send_seq
;
}
guint16
jabber_ibb_session_get_recv_seq
(
const
JabberIBBSession
*
sess
)
{
return
sess
->
recv_seq
;
}
JabberIBBSessionState
jabber_ibb_session_get_state
(
const
JabberIBBSession
*
sess
)
{
return
sess
->
state
;
}
gsize
jabber_ibb_session_get_block_size
(
const
JabberIBBSession
*
sess
)
{
return
sess
->
block_size
;
}
void
jabber_ibb_session_set_block_size
(
JabberIBBSession
*
sess
,
gsize
size
)
{
if
(
jabber_ibb_session_get_state
(
sess
)
==
JABBER_IBB_SESSION_NOT_OPENED
)
{
sess
->
block_size
=
size
;
}
else
{
purple_debug_error
(
"jabber"
,
"Can't set block size on an open IBB session
\n
"
);
}
}
gsize
jabber_ibb_session_get_max_data_size
(
const
JabberIBBSession
*
sess
)
{
return
(
gsize
)
floor
((
sess
->
block_size
-
2
)
*
(
float
)
3
/
4
);
}
gpointer
jabber_ibb_session_get_user_data
(
JabberIBBSession
*
sess
)
{
return
sess
->
user_data
;
}
void
jabber_ibb_session_set_opened_callback
(
JabberIBBSession
*
sess
,
JabberIBBOpenedCallback
*
cb
)
{
sess
->
opened_cb
=
cb
;
}
void
jabber_ibb_session_set_data_sent_callback
(
JabberIBBSession
*
sess
,
JabberIBBSentCallback
*
cb
)
{
sess
->
data_sent_cb
=
cb
;
}
void
jabber_ibb_session_set_closed_callback
(
JabberIBBSession
*
sess
,
JabberIBBClosedCallback
*
cb
)
{
sess
->
closed_cb
=
cb
;
}
void
jabber_ibb_session_set_data_received_callback
(
JabberIBBSession
*
sess
,
JabberIBBDataCallback
*
cb
)
{
sess
->
data_received_cb
=
cb
;
}
void
jabber_ibb_session_set_error_callback
(
JabberIBBSession
*
sess
,
JabberIBBErrorCallback
*
cb
)
{
sess
->
error_cb
=
cb
;
}
static
void
jabber_ibb_session_opened_cb
(
JabberStream
*
js
,
const
char
*
from
,
JabberIqType
type
,
const
char
*
id
,
PurpleXmlNode
*
packet
,
gpointer
data
)
{
JabberIBBSession
*
sess
=
(
JabberIBBSession
*
)
data
;
if
(
type
==
JABBER_IQ_ERROR
)
{
sess
->
state
=
JABBER_IBB_SESSION_ERROR
;
}
else
{
sess
->
state
=
JABBER_IBB_SESSION_OPENED
;
}
if
(
sess
->
opened_cb
)
{
sess
->
opened_cb
(
sess
);
}
}
void
jabber_ibb_session_open
(
JabberIBBSession
*
sess
)
{
if
(
jabber_ibb_session_get_state
(
sess
)
!=
JABBER_IBB_SESSION_NOT_OPENED
)
{
purple_debug_error
(
"jabber"
,
"jabber_ibb_session called on an already open stream
\n
"
);
}
else
{
JabberIq
*
set
=
jabber_iq_new
(
sess
->
js
,
JABBER_IQ_SET
);
PurpleXmlNode
*
open
=
purple_xmlnode_new
(
"open"
);
gchar
block_size
[
10
];
purple_xmlnode_set_attrib
(
set
->
node
,
"to"
,
jabber_ibb_session_get_who
(
sess
));
purple_xmlnode_set_namespace
(
open
,
NS_IBB
);
purple_xmlnode_set_attrib
(
open
,
"sid"
,
jabber_ibb_session_get_sid
(
sess
));
g_snprintf
(
block_size
,
sizeof
(
block_size
),
"%"
G_GSIZE_FORMAT
,
jabber_ibb_session_get_block_size
(
sess
));
purple_xmlnode_set_attrib
(
open
,
"block-size"
,
block_size
);
purple_xmlnode_insert_child
(
set
->
node
,
open
);
jabber_iq_set_callback
(
set
,
jabber_ibb_session_opened_cb
,
sess
);
jabber_iq_send
(
set
);
}
}
void
jabber_ibb_session_close
(
JabberIBBSession
*
sess
)
{
JabberIBBSessionState
state
=
jabber_ibb_session_get_state
(
sess
);
if
(
state
!=
JABBER_IBB_SESSION_OPENED
&&
state
!=
JABBER_IBB_SESSION_ERROR
)
{
purple_debug_error
(
"jabber"
,
"jabber_ibb_session_close called on a session that has not been"
"opened
\n
"
);
}
else
{
JabberIq
*
set
=
jabber_iq_new
(
jabber_ibb_session_get_js
(
sess
),
JABBER_IQ_SET
);
PurpleXmlNode
*
close
=
purple_xmlnode_new
(
"close"
);
purple_xmlnode_set_attrib
(
set
->
node
,
"to"
,
jabber_ibb_session_get_who
(
sess
));
purple_xmlnode_set_namespace
(
close
,
NS_IBB
);
purple_xmlnode_set_attrib
(
close
,
"sid"
,
jabber_ibb_session_get_sid
(
sess
));
purple_xmlnode_insert_child
(
set
->
node
,
close
);
jabber_iq_send
(
set
);
sess
->
state
=
JABBER_IBB_SESSION_CLOSED
;
}
}
void
jabber_ibb_session_accept
(
JabberIBBSession
*
sess
)
{
JabberIq
*
result
=
jabber_iq_new
(
jabber_ibb_session_get_js
(
sess
),
JABBER_IQ_RESULT
);
purple_xmlnode_set_attrib
(
result
->
node
,
"to"
,
jabber_ibb_session_get_who
(
sess
));
jabber_iq_set_id
(
result
,
sess
->
id
);
jabber_iq_send
(
result
);
sess
->
state
=
JABBER_IBB_SESSION_OPENED
;
}
static
void
jabber_ibb_session_send_acknowledge_cb
(
JabberStream
*
js
,
const
char
*
from
,
JabberIqType
type
,
const
char
*
id
,
PurpleXmlNode
*
packet
,
gpointer
data
)
{
JabberIBBSession
*
sess
=
(
JabberIBBSession
*
)
data
;
if
(
sess
)
{
/* reset callback */
g_free
(
sess
->
last_iq_id
);
sess
->
last_iq_id
=
NULL
;
if
(
type
==
JABBER_IQ_ERROR
)
{
jabber_ibb_session_close
(
sess
);
sess
->
state
=
JABBER_IBB_SESSION_ERROR
;
if
(
sess
->
error_cb
)
{
sess
->
error_cb
(
sess
);
}
}
else
{
if
(
sess
->
data_sent_cb
)
{
sess
->
data_sent_cb
(
sess
);
}
}
}
else
{
/* the session has gone away, it was probably cancelled */
purple_debug_info
(
"jabber"
,
"got response from send data, but IBB session is no longer active
\n
"
);
}
}
void
jabber_ibb_session_send_data
(
JabberIBBSession
*
sess
,
gconstpointer
data
,
gsize
size
)
{
JabberIBBSessionState
state
=
jabber_ibb_session_get_state
(
sess
);
purple_debug_info
(
"jabber"
,
"sending data block of %"
G_GSIZE_FORMAT
" bytes on IBB stream
\n
"
,
size
);
if
(
state
!=
JABBER_IBB_SESSION_OPENED
)
{
purple_debug_error
(
"jabber"
,
"trying to send data on a non-open IBB session
\n
"
);
}
else
if
(
size
>
jabber_ibb_session_get_max_data_size
(
sess
))
{
purple_debug_error
(
"jabber"
,
"trying to send a too large packet in the IBB session
\n
"
);
}
else
{
JabberIq
*
set
=
jabber_iq_new
(
jabber_ibb_session_get_js
(
sess
),
JABBER_IQ_SET
);
PurpleXmlNode
*
data_element
=
purple_xmlnode_new
(
"data"
);
char
*
base64
=
g_base64_encode
(
data
,
size
);
char
seq
[
10
];
g_snprintf
(
seq
,
sizeof
(
seq
),
"%u"
,
jabber_ibb_session_get_send_seq
(
sess
));
purple_xmlnode_set_attrib
(
set
->
node
,
"to"
,
jabber_ibb_session_get_who
(
sess
));
purple_xmlnode_set_namespace
(
data_element
,
NS_IBB
);
purple_xmlnode_set_attrib
(
data_element
,
"sid"
,
jabber_ibb_session_get_sid
(
sess
));
purple_xmlnode_set_attrib
(
data_element
,
"seq"
,
seq
);
purple_xmlnode_insert_data
(
data_element
,
base64
,
-1
);
purple_xmlnode_insert_child
(
set
->
node
,
data_element
);
purple_debug_info
(
"jabber"
,
"IBB: setting send <iq/> callback for session %p %s
\n
"
,
sess
,
sess
->
sid
);
jabber_iq_set_callback
(
set
,
jabber_ibb_session_send_acknowledge_cb
,
sess
);
sess
->
last_iq_id
=
g_strdup
(
purple_xmlnode_get_attrib
(
set
->
node
,
"id"
));
purple_debug_info
(
"jabber"
,
"IBB: set sess->last_iq_id: %s
\n
"
,
sess
->
last_iq_id
);
jabber_iq_send
(
set
);
g_free
(
base64
);
(
sess
->
send_seq
)
++
;
}
}
static
void
jabber_ibb_send_error_response
(
JabberStream
*
js
,
const
char
*
to
,
const
char
*
id
)
{
JabberIq
*
result
=
jabber_iq_new
(
js
,
JABBER_IQ_ERROR
);
PurpleXmlNode
*
error
=
purple_xmlnode_new
(
"error"
);
PurpleXmlNode
*
item_not_found
=
purple_xmlnode_new
(
"item-not-found"
);
purple_xmlnode_set_namespace
(
item_not_found
,
NS_XMPP_STANZAS
);
purple_xmlnode_set_attrib
(
error
,
"code"
,
"440"
);
purple_xmlnode_set_attrib
(
error
,
"type"
,
"cancel"
);
jabber_iq_set_id
(
result
,
id
);
purple_xmlnode_set_attrib
(
result
->
node
,
"to"
,
to
);
purple_xmlnode_insert_child
(
error
,
item_not_found
);
purple_xmlnode_insert_child
(
result
->
node
,
error
);
jabber_iq_send
(
result
);
}
void
jabber_ibb_parse
(
JabberStream
*
js
,
const
char
*
who
,
JabberIqType
type
,
const
char
*
id
,
PurpleXmlNode
*
child
)
{
const
char
*
name
=
child
->
name
;
gboolean
data
=
purple_strequal
(
name
,
"data"
);
gboolean
close
=
purple_strequal
(
name
,
"close"
);
gboolean
open
=
purple_strequal
(
name
,
"open"
);
const
gchar
*
sid
=
(
data
||
close
)
?
purple_xmlnode_get_attrib
(
child
,
"sid"
)
:
NULL
;
JabberIBBSession
*
sess
=
sid
?
g_hash_table_lookup
(
jabber_ibb_sessions
,
sid
)
:
NULL
;
if
(
sess
)
{
if
(
!
purple_strequal
(
who
,
jabber_ibb_session_get_who
(
sess
)))
{
/* the iq comes from a different JID than the remote JID of the
session, ignore it */
purple_debug_error
(
"jabber"
,
"Got IBB iq from wrong JID, ignoring
\n
"
);
}
else
if
(
data
)
{
const
gchar
*
seq_attr
=
purple_xmlnode_get_attrib
(
child
,
"seq"
);
guint16
seq
=
(
seq_attr
?
atoi
(
seq_attr
)
:
0
);
/* reject the data, and set the session in error if we get an
out-of-order packet */
if
(
seq_attr
&&
seq
==
jabber_ibb_session_get_recv_seq
(
sess
))
{
/* sequence # is the expected... */
JabberIq
*
result
=
jabber_iq_new
(
js
,
JABBER_IQ_RESULT
);
jabber_iq_set_id
(
result
,
id
);
purple_xmlnode_set_attrib
(
result
->
node
,
"to"
,
who
);
if
(
sess
->
data_received_cb
)
{
gchar
*
base64
=
purple_xmlnode_get_data
(
child
);
gsize
size
;
gpointer
rawdata
=
g_base64_decode
(
base64
,
&
size
);
g_free
(
base64
);
if
(
rawdata
)
{
purple_debug_info
(
"jabber"
,
"got %"
G_GSIZE_FORMAT
" bytes of data on IBB stream
\n
"
,
size
);
/* we accept other clients to send up to block-size
of _unencoded_ data, since there's been some confusions
regarding the interpretation of this attribute
(including previous versions of libpurple) */
if
(
size
>
jabber_ibb_session_get_block_size
(
sess
))
{
purple_debug_error
(
"jabber"
,
"IBB: received a too large packet
\n
"
);
if
(
sess
->
error_cb
)
sess
->
error_cb
(
sess
);
g_free
(
rawdata
);
return
;
}
else
{
purple_debug_info
(
"jabber"
,
"calling IBB callback for received data
\n
"
);
sess
->
data_received_cb
(
sess
,
rawdata
,
size
);
}
g_free
(
rawdata
);
}
else
{
purple_debug_error
(
"jabber"
,
"IBB: invalid BASE64 data received
\n
"
);
if
(
sess
->
error_cb
)
sess
->
error_cb
(
sess
);
return
;
}
}
(
sess
->
recv_seq
)
++
;
jabber_iq_send
(
result
);
}
else
{
purple_debug_error
(
"jabber"
,
"Received an out-of-order/invalid IBB packet
\n
"
);
sess
->
state
=
JABBER_IBB_SESSION_ERROR
;
if
(
sess
->
error_cb
)
{
sess
->
error_cb
(
sess
);
}
}
}
else
if
(
close
)
{
sess
->
state
=
JABBER_IBB_SESSION_CLOSED
;
purple_debug_info
(
"jabber"
,
"IBB: received close
\n
"
);
if
(
sess
->
closed_cb
)
{
purple_debug_info
(
"jabber"
,
"IBB: calling closed handler
\n
"
);
sess
->
closed_cb
(
sess
);
}
}
}
else
if
(
open
)
{
JabberIq
*
result
;
const
GList
*
iterator
;
/* run all open handlers registered until one returns true */
for
(
iterator
=
open_handlers
;
iterator
;
iterator
=
g_list_next
(
iterator
))
{
JabberIBBOpenHandler
*
handler
=
iterator
->
data
;
if
(
handler
(
js
,
who
,
id
,
child
))
{
result
=
jabber_iq_new
(
js
,
JABBER_IQ_RESULT
);
purple_xmlnode_set_attrib
(
result
->
node
,
"to"
,
who
);
jabber_iq_set_id
(
result
,
id
);
jabber_iq_send
(
result
);
return
;
}
}
/* no open callback returned success, reject */
jabber_ibb_send_error_response
(
js
,
who
,
id
);
}
else
{
/* send error reply */
jabber_ibb_send_error_response
(
js
,
who
,
id
);
}
}
void
jabber_ibb_register_open_handler
(
JabberIBBOpenHandler
*
cb
)
{
open_handlers
=
g_list_append
(
open_handlers
,
cb
);
}
void
jabber_ibb_unregister_open_handler
(
JabberIBBOpenHandler
*
cb
)
{
open_handlers
=
g_list_remove
(
open_handlers
,
cb
);
}
void
jabber_ibb_init
(
void
)
{
jabber_ibb_sessions
=
g_hash_table_new
(
g_str_hash
,
g_str_equal
);
jabber_add_feature
(
NS_IBB
,
NULL
);
jabber_iq_register_handler
(
"close"
,
NS_IBB
,
jabber_ibb_parse
);
jabber_iq_register_handler
(
"data"
,
NS_IBB
,
jabber_ibb_parse
);
jabber_iq_register_handler
(
"open"
,
NS_IBB
,
jabber_ibb_parse
);
}
void
jabber_ibb_uninit
(
void
)
{
g_hash_table_destroy
(
jabber_ibb_sessions
);
g_list_free
(
open_handlers
);
jabber_ibb_sessions
=
NULL
;
open_handlers
=
NULL
;
}