ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-XMPP2/lib/Net/XMPP2/Writer.pm
Revision: 1.3
Committed: Thu Jan 25 13:17:06 2007 UTC (19 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.2: +184 -19 lines
Log Message:
extended send_presence and send_message, untested.
wrote some documentation to XMPP.pm and Writer.pm and Parser.pm.

File Contents

# User Rev Content
1 elmex 1.1 package Net::XMPP2::Writer;
2     use warnings;
3     use strict;
4     use XML::Writer;
5     use Authen::SASL;
6     use MIME::Base64;
7     use Net::XMPP2::Namespaces qw/xmpp_ns/;
8    
9     =head1 NAME
10    
11     Net::XMPP2::Writer - A XML writer for XMPP
12    
13     =head1 SYNOPSIS
14    
15     use Net::XMPP2::Writer;
16     ...
17    
18 elmex 1.3 =head1 DESCRIPTION
19    
20     This module contains some helper functions for writing XMPP XML,
21     which is not real XML at all ;-( I use L<XML::Writer> and tune it
22     until it creates XML that is accepted by most servers propably
23     (all of the XMPP servers i tested yet work (jabberd14, jabberd2, ejabberd).
24    
25     I hope the semantics of L<XML::Writer> don't change much over the future,
26     but if they do and you run into problems, please report them!
27    
28     The whole XML concept of XMPP is fundamentally broken anyway. It's supposed
29     to be an subset of XML. But a subset of XML productions is not XML. Strictly
30     speaking you need a special XMPP XML parser and writer to be 100% conformant.
31    
32     But i try to be as XML XMPP conformant as possible (it should be around 99-100%).
33     But it's hard to say what XML is conformant, as the specifications of XMPP XML and XML
34     are contradicting. For example XMPP also says you only have to generated and accept
35     utf-8 encodings of XML, but the XML recommendation says that each parser has
36     to accept utf-8 E<and> utf-16. So, what do you do? Do you use a XML conformant parser
37     or do you write your own?
38    
39     I'm using XML::Parser::Expat as expat does support parsing of broken (aka 'partial')
40     XML documents, as XMPP requires. Another argument is that if you capture a XMPP
41     conversation to the end, and even if a '</stream:stream>' tag was captured, you
42     wont have a valid XML document. The problem is that you have to resent a <stream> tag
43     after TLS and SASL authentication each!
44    
45     But well... Net::XMPP2 does it's best with expat to cope with the fundamental brokeness
46     of XML in XMPP.
47    
48     Back to the issue with XML generation: I've discoverd that many XMPP servers (eg.
49     jabberd14 and ejabberd) have problems with XML namespaces. Thats the reason why
50     i'm assigning the namespace prefixes manually: The servers just don't accept validly
51     namespaced XML. The draft 3921bis does even state that a client SHOULD generate a 'stream'
52     prefix for the <stream> tag.
53    
54     I advice you to explictly set the namespaces too if you generate XML for XMPP yourself,
55     at least until all or most of the XMPP servers have been fixed. Which might take some
56     years :-) And maybe will happen never.
57    
58 elmex 1.1 =head1 METHODS
59    
60     =head2 new (%args)
61    
62     This methods takes following arguments:
63    
64     =over 4
65    
66     =item write_cb
67    
68     The callback that is called when a XML stanza was completly written
69     and is ready for transfer. The first argument of the callback
70     will be the character data to send to the socket.
71    
72     And calls C<init>.
73    
74     =back
75    
76     =cut
77    
78     sub new {
79     my $this = shift;
80     my $class = ref($this) || $this;
81     my $self = { write_cb => sub {}, @_ };
82     bless $self, $class;
83     $self->init;
84     return $self;
85     }
86    
87     =head2 init
88    
89     (Re)initializes the writer.
90    
91     =cut
92    
93     sub init {
94     my ($self) = @_;
95     $self->{write_buf} = "";
96     $self->{writer} = XML::Writer->new (OUTPUT => \$self->{write_buf}, NAMESPACES => 1);
97     }
98    
99     =head2 flush ()
100    
101     This method flushes the internal write buffer and will invoke the C<write_cb>
102     callback. (see also C<new ()> above)
103    
104     =cut
105    
106     sub flush {
107     my ($self) = @_;
108     $self->{write_cb}->(substr $self->{write_buf}, 0, (length $self->{write_buf}), '');
109     }
110    
111     =head2 send_init_stream ($domain)
112    
113     This method will generate a XMPP stream header. C<$domain> has to be the
114     domain of the server (or endpoint) we want to connect to.
115    
116     =cut
117    
118     sub send_init_stream {
119     my ($self, $language, $domain) = @_;
120    
121     my $w = $self->{writer};
122     $w->xmlDecl ('UTF-8');
123     $w->addPrefix (xmpp_ns ('stream'), 'stream');
124     $w->addPrefix (xmpp_ns ('client'), '');
125     $w->forceNSDecl ('jabber:client');
126     $w->startTag (
127     [xmpp_ns ('stream'), 'stream'],
128     to => $domain,
129     version => '1.0',
130     [xmpp_ns ('xml'), 'lang'] => $language
131     );
132     $self->flush;
133     }
134    
135     =head2 send_end_of_stream
136    
137     Sends end of the stream.
138    
139     =cut
140    
141     sub send_end_of_stream {
142     my ($self) = @_;
143     my $w = $self->{writer};
144     $w->endTag ([xmpp_ns ('stream'), 'stream']);
145     $self->flush;
146     }
147    
148     =head2 send_sasl_auth ($mechanisms)
149    
150     This methods sends the start of a SASL authentication. C<$mechanisms> is
151     a string with space seperated mechanisms that are supported by the other
152     end.
153    
154     =cut
155    
156     sub send_sasl_auth {
157     my ($self, $mechanisms, $user, $domain, $pass) = @_;
158    
159     my $sasl = Authen::SASL->new (
160     mechanism => $mechanisms,
161     callback => {
162     authname => $user,
163     user => $user,
164     pass => $pass,
165     }
166     );
167    
168     $self->{sasl} = $sasl->client_new ('xmpp', $domain);
169    
170     my $w = $self->{writer};
171     $w->addPrefix (xmpp_ns ('sasl'), '');
172     $w->startTag ([xmpp_ns ('sasl'), 'auth'], mechanism => $self->{sasl}->mechanism);
173     $w->characters (MIME::Base64::encode_base64 ($self->{sasl}->client_start, ''));
174     $w->endTag;
175     $self->flush;
176     }
177    
178     =head2 send_sasl_response ($challenge)
179    
180     This method generated the SASL authentication response to a C<$challenge>.
181     You must not call this method without calling C<send_sasl_auth ()> before.
182    
183     =cut
184    
185     sub send_sasl_response {
186     my ($self, $challenge) = @_;
187     $challenge = MIME::Base64::decode_base64 ($challenge);
188     my $ret = '';
189     unless ($challenge =~ /rspauth=/) { # rspauth basically means: we are done
190     $ret = $self->{sasl}->client_step ($challenge);
191     unless ($ret) {
192     die "Error in SASL authentication in client step with challenge: '$challenge'\n";
193     }
194     }
195     my $w = $self->{writer};
196     $w->addPrefix (xmpp_ns ('sasl'), '');
197     $w->startTag ([xmpp_ns ('sasl'), 'response']);
198     $w->characters (MIME::Base64::encode_base64 ($ret, ''));
199     $w->endTag;
200     $self->flush;
201     }
202    
203 elmex 1.2 =head2 send_starttls
204    
205     Sends the starttls command to the server.
206    
207     =cut
208    
209     sub send_starttls {
210     my ($self) = @_;
211     my $w = $self->{writer};
212     $w->addPrefix (xmpp_ns ('tls'), '');
213     $w->emptyTag ([xmpp_ns ('tls'), 'starttls']);
214     $self->flush;
215     }
216    
217 elmex 1.1 =head2 send_iq ($id, $type, $create_cb, %attrs)
218    
219 elmex 1.3 This method sends an IQ stanza of type C<$type> (to be compliant
220     only use: 'get', 'set', 'result' and 'error'). C<$create_cb>
221 elmex 1.1 will be called with an XML::Writer instance as first argument.
222     C<$create_cb> should be used to fill the IQ xml stanza.
223 elmex 1.3 If C<$create_cb> is undefined an empty tag will be generated.
224 elmex 1.1
225     C<%attrs> should have further attributes for the IQ stanza tag.
226     For example 'to' or 'from'. If the C<%attrs> contain a 'lang' attribute
227     it will be put into the 'xml' namespace.
228    
229 elmex 1.3 C<$id> is the id to give this IQ stanza and is mandatory in this API.
230 elmex 1.1
231     =cut
232    
233     sub send_iq {
234     my ($self, $id, $type, $create_cb, %attrs) = @_;
235     my $w = $self->{writer};
236     $w->addPrefix (xmpp_ns ('bind'), '');
237     my (@from) = ($self->{jid} ? (from => $self->{jid}) : ());
238     if ($attrs{lang}) {
239     push @from, ([ xmpp_ns ('xml'), 'lang' ] => delete $attrs{leng})
240     }
241 elmex 1.3 if (defined $create_cb) {
242     $w->startTag ('iq', id => $id, type => $type, @from, %attrs);
243     $create_cb->($w);
244     $w->endTag;
245     } else {
246     $w->emptyTag ('iq', id => $id, type => $type, @from, %attrs);
247     }
248     $self->flush;
249     }
250    
251     =head2 send_presence ($id, $type, $create_cb, %attrs)
252    
253     Sends a presence stanza.
254    
255     C<$create_cb> has the same meaning as for C<send_iq>.
256     C<%attrs> will let you pass further optional arguments like 'to'.
257    
258     C<$type> is the type of the presence, which may be one of:
259    
260     unavailable, subscribe, subscribed, unsubscribe, unsubscribed, probe, error
261    
262     Or something completly different if you don't like the RFC 3921 :-)
263    
264     C<%attrs> contains further attributes for the presence tag or may contain one of the
265     following exceptional keys:
266    
267     If C<%attrs> contains a 'show' key: a child xml tag with that name will be geenerated
268     with the value as the content, which should be one of 'away', 'chat', 'dnd' and 'xa'.
269    
270     If C<%attrs> contains a 'status' key: a child xml tag with that name will be generated
271     with the value as content. If the value of the 'status' key is an hash reference
272     the keys will be interpreted as language identifiers for the xml:lang attribute
273     of each status element. If one of these keys is the empty string '' no xml:lang attribute
274     will be generated for it. The values will be the character content of the status tags.
275    
276     If C<%attrs> contains a 'priority' key: a child xml tag with that name will be generated
277     with the value as content, which must be a number between -128 and +127.
278    
279     Note: If C<$create_cb> is undefined and one of the above attributes (show,
280     status or priority) were given, the generates presence tag won't be empty.
281    
282     =cut
283    
284     sub _generate_key_xml {
285     my ($w, $key, $value) = @_;
286     $w->startTag ($key);
287     $w->characters ($value);
288 elmex 1.1 $w->endTag;
289     }
290    
291 elmex 1.3 sub _generate_key_xmls {
292     my ($w, $key, $value) = @_;
293     if (ref ($value) eq 'HASH') {
294     for (keys %$value) {
295     $w->startTag ($key, [xmpp_ns ('xml'), 'lang'] => $_);
296     $w->characters ($value->{$_});
297     $w->endTag;
298     }
299     } else {
300     $w->startTag ($key);
301     $w->characters ($value);
302     $w->endTag;
303     }
304 elmex 1.1 }
305    
306     sub send_presence {
307 elmex 1.3 my ($self, $type, $create_cb, %attrs) = @_;
308    
309 elmex 1.1 my $w = $self->{writer};
310     $w->addPrefix (xmpp_ns ('client'), '');
311 elmex 1.3
312     my @add;
313     push @add, (type => $type) if defined $typ;
314    
315     if (defined $create_cb) {
316     $w->startTag ('presence', @add, %attrs);
317     _generate_key_xml (show => $attrs{show}) if defined $attrs{show};
318     _generate_key_xml (priority => $attrs{priority}) if defined $attrs{priority};
319     _generate_key_xmls (status => $attrs{status}) if defined $attrs{status};
320     $create_cb->($w);
321     $w->endTag;
322     } else {
323     if (exists $attrs{show} or $attrs{priority} or $attrs{status}) {
324     $w->startTag ('presence', @add, %attrs);
325     _generate_key_xml (show => $attrs{show}) if defined $attrs{show};
326     _generate_key_xml (priority => $attrs{priority}) if defined $attrs{priority};
327     _generate_key_xmls (status => $attrs{status}) if defined $attrs{status};
328     $w->endTag;
329     } else {
330     $w->emptyTag ('presence', @add, %attrs);
331     }
332     }
333    
334 elmex 1.1 $self->flush;
335     }
336    
337 elmex 1.3 =head2 send_message ($id, $to, $type, $create_cb, %attrs)
338    
339     Sends a message stanza.
340    
341     C<$to> is the destination JID of the message. C<$type> is
342     the type of the message, and if it is undefined it will default to 'chat'.
343     C<$type> must be one of the following: 'chat', 'error', 'groupchat', 'headline'
344     or 'normal'.
345    
346     C<$create_cb> has the same meaning as in C<send_iq>.
347    
348     C<%attrs> contains further attributes for the message tag or may contain one of the
349     following exceptional keys:
350    
351     If C<%attrs> contains a 'body' key: a child xml tag with that name will be generated
352     with the value as content. If the value of the 'body' key is an hash reference
353     the keys will be interpreted as language identifiers for the xml:lang attribute
354     of each body element. If one of these keys is the empty string '' no xml:lang attribute
355     will be generated for it. The values will be the character content of the body tags.
356    
357     If C<%attrs> contains a 'subject' key: a child xml tag with that name will be generated
358     with the value as content. If the value of the 'subject' key is an hash reference
359     the keys will be interpreted as language identifiers for the xml:lang attribute
360     of each subject element. If one of these keys is the empty string '' no xml:lang attribute
361     will be generated for it. The values will be the character content of the subject tags.
362    
363     If C<%attrs> contains a 'thread' key: a child xml tag with that name will be generated
364     and the value will be the character content.
365    
366     =cut
367    
368 elmex 1.1 sub send_message {
369 elmex 1.3 my ($self, $to, $type, $create_cb, %attrs) = @_;
370    
371 elmex 1.1 my $w = $self->{writer};
372     $w->addPrefix (xmpp_ns ('client'), '');
373 elmex 1.3
374     $type ||= 'chat';
375    
376     if (defined $create_cb) {
377     $w->startTag ('message', to => $to, type => $type, %attrs);
378     _generate_key_xmls (subject => $attrs{subject}) if defined $attrs{subject};
379     _generate_key_xmls (body => $attrs{body}) if defined $attrs{body};
380     _generate_key_xml (thread => $attrs{thread}) if defined $attrs{thread};
381     $create_cb->($w);
382     $w->endTag;
383     } else {
384     if (exists $attrs{subject} or $attrs{body} or $attrs{thread}) {
385     $w->startTag ('message', to => $to, type => $type, %attrs);
386     _generate_key_xmls (subject => $attrs{subject}) if defined $attrs{subject};
387     _generate_key_xmls (body => $attrs{body}) if defined $attrs{body};
388     _generate_key_xml (thread => $attrs{thread}) if defined $attrs{thread};
389     $w->endTag;
390     } else {
391     $w->emptyTag ('message', to => $to, type => $type, %attrs);
392     }
393     }
394    
395 elmex 1.1 $self->flush;
396     }
397    
398     =head1 AUTHOR
399    
400     Robin Redeker, C<< <elmex at ta-sa.org> >>
401    
402     =head1 BUGS
403    
404     Please report any bugs or feature requests to
405     C<bug-net-xmpp2 at rt.cpan.org>, or through the web interface at
406     L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-XMPP2>.
407     I will be notified, and then you'll automatically be notified of progress on
408     your bug as I make changes.
409    
410     =head1 SUPPORT
411    
412     You can find documentation for this module with the perldoc command.
413    
414     perldoc Net::XMPP2
415    
416     You can also look for information at:
417    
418     =over 4
419    
420     =item * AnnoCPAN: Annotated CPAN documentation
421    
422     L<http://annocpan.org/dist/Net-XMPP2>
423    
424     =item * CPAN Ratings
425    
426     L<http://cpanratings.perl.org/d/Net-XMPP2>
427    
428     =item * RT: CPAN's request tracker
429    
430     L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-XMPP2>
431    
432     =item * Search CPAN
433    
434     L<http://search.cpan.org/dist/Net-XMPP2>
435    
436     =back
437    
438     =head1 ACKNOWLEDGEMENTS
439    
440     =head1 COPYRIGHT & LICENSE
441    
442     Copyright 2007 Robin Redeker, all rights reserved.
443    
444     This program is free software; you can redistribute it and/or modify it
445     under the same terms as Perl itself.
446    
447     =cut
448    
449     1; # End of Net::XMPP2