File Coverage

blib/lib/AnyEvent/XMPP/Writer.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package AnyEvent::XMPP::Writer;
2 1     1   1117 use strict;
  1         2  
  1         29  
3 1     1   1021 use XML::Writer;
  1         6276  
  1         23  
4 1     1   754 use Authen::SASL qw/Perl/;
  1         1503  
  1         7  
5 1     1   7064 use MIME::Base64;
  1         843  
  1         83  
6 1     1   8 use AnyEvent::XMPP::Namespaces qw/xmpp_ns/;
  1         2  
  1         52  
7 1     1   61 use AnyEvent::XMPP::Util qw/simxml filter_xml_chars filter_xml_attr_hash_chars/;
  0            
  0            
8             use Digest::SHA qw/sha1_hex/;
9             use Encode;
10              
11             =head1 NAME
12              
13             AnyEvent::XMPP::Writer - "XML" writer for XMPP
14              
15             =head1 SYNOPSIS
16              
17             use AnyEvent::XMPP::Writer;
18             ...
19              
20             =head1 DESCRIPTION
21              
22             This module contains some helper functions for writing XMPP "XML", which is not
23             real XML at all ;-( I use L and tune it until it creates "XML"
24             that is accepted by most servers propably (all of the XMPP servers I tested
25             should work (jabberd14, jabberd2, ejabberd, googletalk).
26              
27             I hope the semantics of L don't change much in the future, but if
28             they do and you run into problems, please report them!
29              
30             The whole "XML" concept of XMPP is fundamentally broken anyway. It's supposed
31             to be an subset of XML. But a subset of XML productions is not XML. Strictly
32             speaking you need a special XMPP "XML" parser and writer to be 100% conformant.
33              
34             On top of that XMPP B you to parse these partial "XML" documents.
35             But a partial XML document is not well-formed, heck, it's not even a XML
36             document! And a parser should bail out with an error. But XMPP doesn't care,
37             it just relies on implementation dependend behaviour of chunked parsing modes
38             for SAX parsing. This functionality isn't even specified by the XML
39             recommendation in any way. The recommendation even says that it's undefined
40             what happens if you process not-well-formed XML documents.
41              
42             But I try to be as XMPP "XML" conformant as possible (it should be around
43             99-100%). But it's hard to say what XML is conformant, as the specifications
44             of XMPP "XML" and XML are contradicting. For example XMPP also says you only
45             have to generated and accept UTF-8 encodings of XML, but the XML recommendation
46             says that each parser has to accept UTF-8 B UTF-16. So, what do you do? Do
47             you use a XML conformant parser or do you write your own?
48              
49             I'm using XML::Parser::Expat because expat knows how to parse broken (aka
50             'partial') "XML" documents, as XMPP requires. Another argument is that if you
51             capture a XMPP conversation to the end, and even if a '' tag
52             was captured, you wont have a valid XML document. The problem is that you have
53             to resent a tag after TLS and SASL authentication each! Awww... I'm
54             repeating myself.
55              
56             But well... AnyEvent::XMPP does it's best with expat to cope with the
57             fundamental brokeness of "XML" in XMPP.
58              
59             Back to the issue with "XML" generation: I've discoverd that many XMPP servers
60             (eg. jabberd14 and ejabberd) have problems with XML namespaces. Thats the
61             reason why I'm assigning the namespace prefixes manually: The servers just
62             don't accept validly namespaced XML. The draft 3921bis does even state that a
63             client SHOULD generate a 'stream' prefix for the tag.
64              
65             I advice you to explicitly set the namespaces too if you generate "XML" for
66             XMPP yourself, at least until all or most of the XMPP servers have been fixed.
67             Which might take some years :-) And maybe will happen never.
68              
69             And another note: As XMPP requires all predefined entity characters to be
70             escaped in character data you need a "XML" writer that will escape everything:
71              
72             RFC 3920 - 11.1. Restrictions:
73              
74             character data or attribute values containing unescaped characters
75             that map to the predefined entities (Section 4.6 therein);
76             such characters MUST be escaped
77              
78             This means:
79             You have to escape '>' in the character data. I don't know whether XML::Writer
80             does that. And I honestly don't care much about this. XMPP is broken by design and
81             I have barely time to writer my own XML parsers and writers to suit their sick taste
82             of "XML". (Do I repeat myself?)
83              
84             I would be happy if they finally say (in RFC3920): "XMPP is NOT XML. It's just
85             XML-like, and some XML utilities allow you to process this kind of XML.".
86              
87             =head1 METHODS
88              
89             =over 4
90              
91             =item B
92              
93             This methods takes following arguments:
94              
95             =over 4
96              
97             =item write_cb
98              
99             The callback that is called when a XML stanza was completely written and is
100             ready for transfer. The first argument of the callback will be the character
101             data to send to the socket.
102              
103             =back
104              
105             And calls C.
106              
107             =cut
108              
109             sub new {
110             my $this = shift;
111             my $class = ref($this) || $this;
112             my $self = {
113             write_cb => sub {},
114             send_iq_cb => sub {},
115             send_msg_cb => sub {},
116             send_pres_cb => sub {},
117             @_
118             };
119             bless $self, $class;
120             $self->init;
121             return $self;
122             }
123              
124             =item B
125              
126             (Re)initializes the writer.
127              
128             =cut
129              
130             sub init {
131             my ($self) = @_;
132             $self->{write_buf} = "";
133             $self->{writer} =
134             XML::Writer->new (OUTPUT => \$self->{write_buf}, NAMESPACES => 1, UNSAFE => 1);
135             }
136              
137             =item B
138              
139             This method flushes the internal write buffer and will invoke the C
140             callback. (see also C above)
141              
142             =cut
143              
144             sub flush {
145             my ($self) = @_;
146             $self->{write_cb}->(substr $self->{write_buf}, 0, (length $self->{write_buf}), '');
147             }
148              
149             =item B
150              
151             This method will generate a XMPP stream header. C<$domain> has to be the
152             domain of the server (or endpoint) we want to connect to.
153              
154             C<$namespace> is the namespace URI or the tag (from L)
155             for the stream namespace. (This is used by L to connect
156             as component to a server). C<$namespace> can also be undefined, in this case
157             the C namespace will be used.
158              
159             =cut
160              
161             sub send_init_stream {
162             my ($self, $language, $domain, $ns, $vers_override) = @_;
163              
164             $ns ||= 'client';
165              
166             my $w = $self->{writer};
167             $w->xmlDecl ();
168             $w->addPrefix (xmpp_ns ('stream'), 'stream');
169             $w->addPrefix (xmpp_ns ($ns), '');
170             $w->forceNSDecl (xmpp_ns ($ns));
171             $w->startTag (
172             [xmpp_ns ('stream'), 'stream'],
173             to => $domain,
174             version => (defined $vers_override ? $vers_override : '1.0'),
175             [xmpp_ns ('xml'), 'lang'] => $language
176             );
177             $self->flush;
178             }
179              
180             =item B
181              
182             This method sends a single space to the server.
183              
184             =cut
185              
186             sub send_whitespace_ping {
187             my ($self) = @_;
188             $self->{writer}->raw (' ');
189             $self->flush;
190             }
191              
192             =item B
193              
194             This method sends a component handshake. Please note that C<$secret>
195             must be XML escaped!
196              
197             =cut
198              
199             sub send_handshake {
200             my ($self, $id, $secret) = @_;
201             my $out_secret = encode ("UTF-8", $secret);
202             my $out = lc sha1_hex ($id . $out_secret);
203             simxml ($self->{writer}, defns => 'component', node => {
204             ns => 'component', name => 'handshake', childs => [ $out ]
205             });
206             $self->flush;
207             }
208              
209             =item B
210              
211             Sends end of the stream.
212              
213             =cut
214              
215             sub send_end_of_stream {
216             my ($self) = @_;
217             my $w = $self->{writer};
218             $w->endTag ([xmpp_ns ('stream'), 'stream']);
219             $self->flush;
220             }
221              
222             =item B
223              
224             This methods sends the start of a SASL authentication. C<$mechanisms> is
225             an array reference, containing the mechanism names that are to be tried.
226              
227             =cut
228              
229             sub send_sasl_auth {
230             my ($self, $mechs, $user, $hostname, $pass) = @_;
231              
232             my $data;
233            
234             my $found_mech = 0;
235             while (!$found_mech) {
236             my $sasl = Authen::SASL->new (
237             mechanism => join (' ', @$mechs),
238             callback => {
239             # XXX: removed authname, because it ensures maximum connectivitiy
240             # along multiple server implementations - XMPP is such a crap
241             # authname => $user . '@' . $domain,
242             user => $user,
243             pass => $pass,
244             }
245             );
246              
247             my $mech = $sasl->client_new ('xmpp', $hostname);
248             $data = $mech->client_start;
249              
250             if (my $e = $mech->error) {
251             @$mechs = grep { $_ ne $mech->mechanism } @$mechs;
252             die "No usable SASL mechanism found (tried: "
253             . join (', ', @$mechs)
254             . ")!\n"
255             unless @$mechs;
256             next;
257             }
258              
259             $found_mech = 1;
260             $self->{sasl} = $mech;
261             }
262              
263             my $w = $self->{writer};
264             $w->addPrefix (xmpp_ns ('sasl'), '');
265             $w->startTag ([xmpp_ns ('sasl'), 'auth'], mechanism => $self->{sasl}->mechanism);
266             $w->characters (MIME::Base64::encode_base64 ($data, ''));
267             $w->endTag;
268             $self->flush;
269             }
270              
271             =item B
272              
273             This method generated the SASL authentication response to a C<$challenge>.
274             You must not call this method without calling C before.
275              
276             =cut
277              
278             sub send_sasl_response {
279             my ($self, $challenge) = @_;
280             $challenge = MIME::Base64::decode_base64 ($challenge);
281             my $ret = '';
282             unless ($challenge =~ /rspauth=/) { # rspauth basically means: we are done
283             $ret = $self->{sasl}->client_step ($challenge);
284             if (my $e = $self->{sasl}->error) {
285             die "Error in SASL authentication in client step with challenge: '" . $e . "'\n";
286             }
287             }
288             my $w = $self->{writer};
289             $w->addPrefix (xmpp_ns ('sasl'), '');
290             $w->startTag ([xmpp_ns ('sasl'), 'response']);
291             $w->characters (MIME::Base64::encode_base64 ($ret, ''));
292             $w->endTag;
293             $self->flush;
294             }
295              
296             =item B
297              
298             Sends the starttls command to the server.
299              
300             =cut
301              
302             sub send_starttls {
303             my ($self) = @_;
304             my $w = $self->{writer};
305             $w->addPrefix (xmpp_ns ('tls'), '');
306             $w->emptyTag ([xmpp_ns ('tls'), 'starttls']);
307             $self->flush;
308             }
309              
310             =item B
311              
312             This method sends an IQ stanza of type C<$type> (to be compliant
313             only use: 'get', 'set', 'result' and 'error').
314              
315             If C<$create_cb> is a code reference it will be called with an XML::Writer
316             instance as first argument, which must be used to fill the IQ stanza. The
317             XML::Writer is in UNSAFE mode, so you can safely use C to write out XML.
318              
319             C<$create_cb> is a hash reference the hash will be used as key=>value arguments
320             for the C function defined in L. C will then
321             be used to generate the contents of the IQ stanza. (This is very convenient
322             when you want to write the contents of stanzas in the code and don't want to
323             build a DOM tree yourself...).
324              
325             If C<$create_cb> is an array reference it's elements will be interpreted as
326             single C<$create_cb> argument (which can either be a hash reference or code
327             reference themself) and executed sequentially.
328              
329             If C<$create_cb> is undefined an empty tag will be generated.
330              
331             Example:
332              
333             $writer->send_iq ('newid', 'get', {
334             defns => 'version',
335             node => { name => 'query', ns => 'version' }
336             }, to => 'jabber.org')
337              
338             C<%attrs> should have further attributes for the IQ stanza tag.
339             For example 'to' or 'from'. If the C<%attrs> contain a 'lang' attribute
340             it will be put into the 'xml' namespace. If the 'to' attribute contains
341             an undef it will be omitted.
342              
343             C<$id> is the id to give this IQ stanza and is mandatory in this API.
344              
345             Please note that all attribute values and character data will be filtered
346             by C (see also L).
347              
348             =cut
349              
350             sub send_iq {
351             my ($self, $id, $type, $create_cb, %attrs) = @_;
352              
353             $create_cb = _trans_create_cb ($create_cb);
354             $create_cb = $self->_fetch_cb_additions (send_iq_cb => $create_cb, $id, $type, \%attrs);
355              
356             my (@from) = ($self->{jid} ? (from => $self->{jid}) : ());
357             if ($attrs{lang}) {
358             push @from, ([ xmpp_ns ('xml'), 'lang' ] => delete $attrs{leng})
359             }
360              
361             unless (defined $attrs{to}) {
362             delete $attrs{to};
363             }
364              
365             push @from, (id => filter_xml_chars $id) if defined $id;
366              
367             filter_xml_attr_hash_chars \%attrs;
368              
369             my $w = $self->{writer};
370             $w->addPrefix (xmpp_ns ('client'), '');
371              
372             if (defined $create_cb) {
373             $w->startTag ([xmpp_ns ('client'), 'iq'], type => $type, @from, %attrs);
374             $create_cb->($w);
375             $w->endTag;
376             } else {
377             $w->emptyTag ([xmpp_ns ('client'), 'iq'], type => $type, @from, %attrs);
378             }
379             $self->flush;
380             }
381              
382             =item B
383              
384             Sends a presence stanza.
385              
386             C<$create_cb> has the same meaning as for C.
387             C<%attrs> will let you pass further optional arguments like 'to'.
388              
389             C<$type> is the type of the presence, which may be one of:
390              
391             unavailable, subscribe, subscribed, unsubscribe, unsubscribed, probe, error
392              
393             Or undef, in case you want to send a 'normal' presence.
394             Or something completely different if you don't like the RFC 3921 :-)
395              
396             C<%attrs> contains further attributes for the presence tag or may contain one of the
397             following exceptional keys:
398              
399             If C<%attrs> contains a 'show' key: a child xml tag with that name will be generated
400             with the value as the content, which should be one of 'away', 'chat', 'dnd' and 'xa'.
401             If it contains an undefined value no such tag will be generated, which usually means
402             that the 'available' presence is meant.
403              
404             If C<%attrs> contains a 'status' key: a child xml tag with that name will be generated
405             with the value as content. If the value of the 'status' key is an hash reference
406             the keys will be interpreted as language identifiers for the xml:lang attribute
407             of each status element. If one of these keys is the empty string '' no xml:lang attribute
408             will be generated for it. The values will be the character content of the status tags.
409              
410             If C<%attrs> contains a 'priority' key: a child xml tag with that name will be generated
411             with the value as content, which must be a number between -128 and +127.
412              
413             Note: If C<$create_cb> is undefined and one of the above attributes (show,
414             status or priority) were given, the generates presence tag won't be empty.
415              
416             Please note that all attribute values and character data will be filtered
417             by C (see also L).
418              
419             =cut
420              
421             sub _generate_key_xml {
422             my ($w, $key, $value) = @_;
423             $w->startTag ($key);
424             $w->characters (filter_xml_chars $value);
425             $w->endTag;
426             }
427              
428             sub _generate_key_xmls {
429             my ($w, $key, $value) = @_;
430             if (ref ($value) eq 'HASH') {
431             for (keys %$value) {
432             $w->startTag ($key, ($_ ne '' ? ([xmpp_ns ('xml'), 'lang'] => $_) : ()));
433             $w->characters (filter_xml_chars $value->{$_});
434             $w->endTag;
435             }
436             } else {
437             $w->startTag ($key);
438             $w->characters (filter_xml_chars $value);
439             $w->endTag;
440             }
441             }
442              
443             sub _trans_create_cb {
444             my ($cb) = @_;
445             return unless defined $cb;
446             if (ref ($cb) eq 'HASH') {
447             my $args = $cb;
448             $cb = sub {
449             my ($w) = @_;
450             simxml ($w, %$args);
451             }
452             } elsif (ref ($cb) eq 'ARRAY') {
453             my @cbs = map { _trans_create_cb ($_) } @$cb;
454             $cb = sub {
455             my ($w) = @_;
456             for (@cbs) { $_->($w) }
457             }
458             }
459             $cb
460             }
461              
462             sub _fetch_cb_additions {
463             my ($self, $key, $create_cb, @args) = @_;
464             my (@add_cbs) = $self->{$key}->(@args);
465             @add_cbs = map { _trans_create_cb ($_) } @add_cbs;
466              
467             if (@add_cbs) {
468             my $crcb = $create_cb;
469             $create_cb = sub {
470             my (@args) = @_;
471             $crcb->(@args) if $crcb;
472             for (@add_cbs) { $_->(@args) }
473             }
474             }
475              
476             $create_cb
477             }
478              
479             sub send_presence {
480             my ($self, $id, $type, $create_cb, %attrs) = @_;
481              
482             $create_cb = _trans_create_cb ($create_cb);
483             $create_cb = $self->_fetch_cb_additions (send_pres_cb => $create_cb, $id, $type, \%attrs);
484              
485             my $w = $self->{writer};
486             $w->addPrefix (xmpp_ns ('client'), '');
487              
488             my @add;
489             push @add, (type => $type) if defined $type;
490             push @add, (id => $id) if defined $id;
491              
492             my %fattrs =
493             map { $_ => $attrs{$_} }
494             grep { my $k = $_; not grep { $k eq $_ } qw/show priority status/ }
495             keys %attrs;
496              
497             filter_xml_attr_hash_chars \%fattrs;
498              
499             if (defined $create_cb) {
500             $w->startTag ([xmpp_ns ('client'), 'presence'], @add, %fattrs);
501             _generate_key_xml ($w, show => $attrs{show}) if defined $attrs{show};
502             _generate_key_xml ($w, priority => $attrs{priority}) if defined $attrs{priority};
503             _generate_key_xmls ($w, status => $attrs{status}) if defined $attrs{status};
504             $create_cb->($w);
505             $w->endTag;
506             } else {
507             if (exists $attrs{show} or $attrs{priority} or $attrs{status}) {
508             $w->startTag ([xmpp_ns ('client'), 'presence'], @add, %fattrs);
509             _generate_key_xml ($w, show => $attrs{show}) if defined $attrs{show};
510             _generate_key_xml ($w, priority => $attrs{priority}) if defined $attrs{priority};
511             _generate_key_xmls ($w, status => $attrs{status}) if defined $attrs{status};
512             $w->endTag;
513             } else {
514             $w->emptyTag ([xmpp_ns ('client'), 'presence'], @add, %fattrs);
515             }
516             }
517              
518             $self->flush;
519             }
520              
521             =item B
522              
523             Sends a message stanza.
524              
525             C<$to> is the destination JID of the message. C<$type> is
526             the type of the message, and if C<$type> is undefined it will default to 'chat'.
527             C<$type> must be one of the following: 'chat', 'error', 'groupchat', 'headline'
528             or 'normal'.
529              
530             C<$create_cb> has the same meaning as in C.
531              
532             C<%attrs> contains further attributes for the message tag or may contain one of the
533             following exceptional keys:
534              
535             If C<%attrs> contains a 'body' key: a child xml tag with that name will be generated
536             with the value as content. If the value of the 'body' key is an hash reference
537             the keys will be interpreted as language identifiers for the xml:lang attribute
538             of each body element. If one of these keys is the empty string '' no xml:lang attribute
539             will be generated for it. The values will be the character content of the body tags.
540              
541             If C<%attrs> contains a 'subject' key: a child xml tag with that name will be generated
542             with the value as content. If the value of the 'subject' key is an hash reference
543             the keys will be interpreted as language identifiers for the xml:lang attribute
544             of each subject element. If one of these keys is the empty string '' no xml:lang attribute
545             will be generated for it. The values will be the character content of the subject tags.
546              
547             If C<%attrs> contains a 'thread' key: a child xml tag with that name will be generated
548             and the value will be the character content.
549              
550             Please note that all attribute values and character data will be filtered
551             by C (see also L).
552              
553             =cut
554              
555             sub send_message {
556             my ($self, $id, $to, $type, $create_cb, %attrs) = @_;
557              
558             $create_cb = _trans_create_cb ($create_cb);
559             $create_cb = $self->_fetch_cb_additions (send_msg_cb => $create_cb, $id, $to, $type, \%attrs);
560              
561             my $w = $self->{writer};
562             $w->addPrefix (xmpp_ns ('client'), '');
563              
564             my @add;
565             push @add, (id => $id) if defined $id;
566              
567             $type ||= 'chat';
568              
569             my %fattrs =
570             map { $_ => $attrs{$_} }
571             grep { my $k = $_; not grep { $k eq $_ } qw/subject body thread/ }
572             keys %attrs;
573              
574             if (defined $create_cb) {
575             $w->startTag ([xmpp_ns ('client'), 'message'], @add, to => $to, type => $type, %fattrs);
576             _generate_key_xmls ($w, subject => $attrs{subject}) if defined $attrs{subject};
577             _generate_key_xmls ($w, body => $attrs{body}) if defined $attrs{body};
578             _generate_key_xml ($w, thread => $attrs{thread}) if defined $attrs{thread};
579             $create_cb->($w);
580             $w->endTag;
581             } else {
582             if (exists $attrs{subject} or $attrs{body} or $attrs{thread}) {
583             $w->startTag ([xmpp_ns ('client'), 'message'], @add, to => $to, type => $type, %fattrs);
584             _generate_key_xmls ($w, subject => $attrs{subject}) if defined $attrs{subject};
585             _generate_key_xmls ($w, body => $attrs{body}) if defined $attrs{body};
586             _generate_key_xml ($w, thread => $attrs{thread}) if defined $attrs{thread};
587             $w->endTag;
588             } else {
589             $w->emptyTag ([xmpp_ns ('client'), 'message'], @add, to => $to, type => $type, %fattrs);
590             }
591             }
592              
593             $self->flush;
594             }
595              
596              
597             =item B
598              
599             C<$error_type> is one of 'cancel', 'continue', 'modify', 'auth' and 'wait'.
600             C<$error> is the name of the error tag child element. If C<$error> is one of
601             the following:
602              
603             'bad-request', 'conflict', 'feature-not-implemented', 'forbidden', 'gone',
604             'internal-server-error', 'item-not-found', 'jid-malformed', 'not-acceptable',
605             'not-allowed', 'not-authorized', 'payment-required', 'recipient-unavailable',
606             'redirect', 'registration-required', 'remote-server-not-found',
607             'remote-server-timeout', 'resource-constraint', 'service-unavailable',
608             'subscription-required', 'undefined-condition', 'unexpected-request'
609              
610             then a default can be select for C<$error_type>, and the argument can be undefined.
611              
612             Note: This method is currently a bit limited in the generation of the xml
613             for the errors, if you need more please contact me.
614              
615             =cut
616              
617             our %STANZA_ERRORS = (
618             'bad-request' => ['modify', 400],
619             'conflict' => ['cancel', 409],
620             'feature-not-implemented' => ['cancel', 501],
621             'forbidden' => ['auth', 403],
622             'gone' => ['modify', 302],
623             'internal-server-error' => ['wait', 500],
624             'item-not-found' => ['cancel', 404],
625             'jid-malformed' => ['modify', 400],
626             'not-acceptable' => ['modify', 406],
627             'not-allowed' => ['cancel', 405],
628             'not-authorized' => ['auth', 401],
629             'payment-required' => ['auth', 402],
630             'recipient-unavailable' => ['wait', 404],
631             'redirect' => ['modify', 302],
632             'registration-required' => ['auth', 407],
633             'remote-server-not-found' => ['cancel', 404],
634             'remote-server-timeout' => ['wait', 504],
635             'resource-constraint' => ['wait', 500],
636             'service-unavailable' => ['cancel', 503],
637             'subscription-required' => ['auth', 407],
638             'undefined-condition' => ['cancel', 500],
639             'unexpected-request' => ['wait', 400],
640             );
641              
642             sub write_error_tag {
643             my ($self, $errstanza, $type, $error) = @_;
644              
645             my $w = $self->{writer};
646              
647             $_->write_on ($w) for $errstanza->nodes;
648              
649             my @add;
650              
651             unless (defined $type and defined $STANZA_ERRORS{$error}) {
652             $type = $STANZA_ERRORS{$error}->[0];
653             }
654              
655             push @add, (code => $STANZA_ERRORS{$error}->[1]);
656              
657             my %add = @add;
658             filter_xml_attr_hash_chars \%add;
659              
660             $w->addPrefix (xmpp_ns ('client'), '');
661             $w->startTag ([xmpp_ns ('client') => 'error'], type => $type, %add);
662             $w->addPrefix (xmpp_ns ('stanzas'), '');
663             $w->emptyTag ([xmpp_ns ('stanzas') => filter_xml_chars $error]);
664             $w->endTag;
665             }
666              
667             =back
668              
669             =head1 AUTHOR
670              
671             Robin Redeker, C<< >>, JID: C<< >>
672              
673             =head1 COPYRIGHT & LICENSE
674              
675             Copyright 2007, 2008 Robin Redeker, all rights reserved.
676              
677             This program is free software; you can redistribute it and/or modify it
678             under the same terms as Perl itself.
679              
680             =cut
681              
682             1; # End of AnyEvent::XMPP