ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-XMPP2/lib/Net/XMPP2/Connection.pm
Revision: 1.1
Committed: Tue Jan 23 15:56:47 2007 UTC (19 years, 8 months ago) by elmex
Branch: MAIN
Log Message:
initial checkin.

File Contents

# User Rev Content
1 elmex 1.1 package Net::XMPP2::Connection;
2     use warnings;
3     use strict;
4     use AnyEvent;
5     use IO::Socket::INET;
6     use Net::XMPP2::Parser;
7     use Net::XMPP2::Writer;
8     use Net::XMPP2::Util;
9     use Net::XMPP2::Namespaces qw/xmpp_ns/;
10     use Net::DNS;
11    
12     our @ISA = qw/Net::XMPP2::SimpleConnection/;
13    
14     =head1 NAME
15    
16     Net::XMPP2::Connection - A XML stream that implements the XMPP RFC 3920.
17    
18     =head1 SYNOPSIS
19    
20     use Net::XMPP2::Connection;
21    
22     my $con =
23     Net::XMPP2::Connection->new (
24     username => "abc",
25     domain => "jabber.org",
26     resource => "Net::XMPP2"
27     );
28    
29     $con->connect or die "Couldn't connect to jabber.org: $!";
30     $con->init;
31     $con->reg_cb (stream_ready => sub { print "XMPP stream ready!\n" });
32    
33     =head1 DESCRIPTION
34    
35     This module represents a XMPP stream as described in RFC 3920. You can issue the basic
36     XMPP XML stanzas with methods like C<send_iq>, C<send_message> and C<send_presence>.
37    
38     And receive events with the C<reg_cb> event framework from the connection.
39    
40     If you need instant messaging stuff please take a look at C<Net::XMPP2::IM::Connection>.
41    
42     =head1 METHODS
43    
44     =head2 new (%args)
45    
46     Following arguments can be passed in C<%args>:
47    
48     =over 4
49    
50     =item language => $tag
51    
52     This should be the language of the human readable contents that
53     will be transmitted over the stream. The default will be 'en'.
54    
55     Please look in RFC 3066 how C<$tag> should look like.
56    
57     =item resource => $resource
58    
59     If this argument is given C<$resource> will be passed as desired
60     resource on resource binding.
61    
62     Note: You have to take care that the stringprep profile for
63     resources can be applied at: C<$resource>. Otherwise the server
64     might signal an error. See L<Net::XMPP2::Util> for utility functions
65     to check this.
66    
67     =item domain => $domain
68    
69     This is the destination host we are going to connect to.
70     As the connection won't be automatically connected use C<connect>
71     to initiate the connect.
72    
73     Note: A SRV RR lookup will be performed to discover the real hostname
74     and port to connect to. See also C<connect>.
75    
76     =item port => $port
77    
78     This is optional, the default port is 5222.
79    
80     Note: A SRV RR lookup will be performed to discover the real hostname
81     and port to connect to. See also C<connect>.
82    
83     =item username => $username
84    
85     This is your C<$username> (the userpart in the JID);
86    
87     Note: You have to take care that the stringprep profile for
88     nodes can be applied at: C<$username>. Otherwise the server
89     might signal an error. See L<Net::XMPP2::Util> for utility functions
90     to check this.
91    
92     =item password => $password
93    
94     This is the password for the C<username> above.
95    
96     =back
97    
98     =cut
99    
100     sub new {
101     my $this = shift;
102     my $class = ref($this) || $this;
103     my $self = { language => 'en', @_ };
104     bless $self, $class;
105    
106     $self->{parser} = new Net::XMPP2::Parser;
107     $self->{writer} = Net::XMPP2::Writer->new (
108     write_cb => sub { $self->write_data ($_[0]) }
109     );
110    
111     $self->{parser}->set_stanza_cb (sub {
112     $self->handle_stanza (@_);
113     });
114    
115     $self->{iq_id} = 1;
116    
117     $self->{disconnect_cb} = sub {
118     my ($host, $port, $message) = @_;
119     $self->event (disconnect => $host, $port, $message);
120     };
121    
122     return $self;
123     }
124    
125     =head2 connect ($no_srv_rr)
126    
127     Try to connect to the domain and port passed in C<new>.
128    
129     A SRV RR lookup will be performed on the domain to discover
130     the host and port to use. If you don't want this set C<$no_srv_rr>
131     to a true value. C<$no_srv_rr> is false by default.
132    
133     As the SRV RR lookup might return multiple host and you fail to
134     connect to one you might just call this function again to try a
135     different host.
136    
137     If C<connect> was successful and we connected a true value is returned.
138     If the connect was unsuccessful undef is returned and C<$!> will be set
139     to the error that occured while connecting.
140    
141     If you want to know whether further connection attempts might be more
142     successful (as SRV RR lookup may return multiple hosts) call C<may_try_connect>
143     (see also C<may_try_connect>).
144    
145     Note that an internal list will be kept of tried hosts. Use
146     C<reset_connect_tries> to reset the internal list of tried hosts.
147    
148     =cut
149    
150     sub connect {
151     my ($self, $no_srv_rr) = @_;
152    
153     my ($host, $port) = ($self->{domain}, $self->{port} || 5222);
154    
155     unless ($no_srv_rr) {
156     my $res = Net::DNS::Resolver->new;
157     my $p = $res->query ('_xmpp-client._tcp.'.$host, 'SRV');
158     if ($p) {
159     my @srvs = grep { $_->type eq 'SRV' } $p->answer;
160     if (@srvs) {
161     @srvs = sort { $a->priority <=> $b->priority } @srvs;
162     @srvs = sort { $b->weight <=> $a->weight } @srvs; # TODO
163     $port = $srvs[0]->port;
164     $host = $srvs[0]->target;
165     }
166     }
167     }
168    
169     if ($self->SUPER::connect ($host, $port)) {
170     $self->event (connect => $host, $port);
171     return 1;
172     } else {
173     return undef;
174     }
175     }
176    
177     =head2 may_try_connect
178    
179     Returns the number of left alternatives of hosts to connect to for the
180     domain passed to C<new>.
181    
182     An internal list of tried hosts will be managed by C<connect> and those
183     hosts will be ignored by a SRV RR lookup (which will be done if you
184     call this function).
185    
186     Use C<reset_connect_tries> to reset the internal list of tried hosts.
187    
188     =cut
189    
190     sub may_try_connect {
191     # TODO
192     }
193    
194     =head2 reset_connect_tries
195    
196     This function resets the internal list of tried hosts for C<connect>.
197     See also C<connect>.
198    
199     =cut
200    
201     sub reset_connect_tries {
202     # TODO
203     }
204    
205     sub handle_data {
206     my ($self, $buf) = @_;
207     $self->event (debug_recv => $$buf);
208     $self->{parser}->feed (substr $$buf, 0, (length $$buf), '');
209     }
210    
211     sub write_data {
212     my ($self, $data) = @_;
213     $self->event (debug_send => $data);
214     $self->SUPER::write_data ($data);
215     }
216    
217     =item reg_cb ($eventname1, $cb1, [$eventname2, $cb2, ...])
218    
219     This method registers a callback C<$cb1> for the event with the
220     name C<$eventname1>. You can also pass multiple of these eventname => callback
221     pairs.
222    
223     To see a documentation of emitted events please take a look at the EVENTS section
224     below.
225    
226     =cut
227    
228     sub reg_cb {
229     my ($self, %regs) = @_;
230    
231     for my $cmd (keys %regs) {
232     my $cb = $regs{$cmd};
233     push @{$self->{events}->{$cmd}}, $cb;
234     }
235    
236     1;
237     }
238    
239     sub event {
240     my ($self, $ev, @arg) = @_;
241    
242     my $nxt = [];
243    
244     for (@{$self->{events}->{lc $ev}}) {
245     $_->($self, @arg) and push @$nxt, $_;
246     }
247    
248     $self->{events}->{lc $ev} = $nxt;
249     }
250    
251     sub handle_stanza {
252     my ($self, $p, $node) = @_;
253    
254     if ($node->eq (stream => 'features')) {
255     $self->event (stream_features => $node);
256     $self->handle_stream_features ($node);
257     } elsif ($node->eq (sasl => 'challenge')) {
258     $self->handle_sasl_challenge ($node);
259     } elsif ($node->eq (sasl => 'success')) {
260     $self->handle_sasl_success ($node);
261     } elsif ($node->eq (client => 'iq')) {
262     $self->handle_iq ($node);
263     } elsif ($node->eq (stream => 'error')) {
264     $self->handle_error ($node);
265     } else {
266     warn "Didn't understood stanza: '" . $node->name . "'";
267     }
268     }
269    
270     =head2 init ($domain)
271    
272     Initiate the XML stream.
273    
274     =cut
275    
276     sub init {
277     my ($self) = @_;
278     $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
279     }
280    
281     =head2 send_iq ($type, $create_cb, $result_cb, %attrs)
282    
283     This method sends an IQ XMPP request.
284    
285     Please take a look at the documentation for C<send_iq> in Net::XMPP2::Writer
286     about the meaning of C<$type>, C<$create_cb> and C<%attrs>.
287    
288     C<$result_cb> will be called when a result was received. The first argument
289     to C<$result_cb> will be a Net::XMPP2::Parser instance and the second
290     will be a Net::XMPP2::Node instance containing the IQ result stanza contents.
291    
292     If the IQ resulted in a stanza error the second argument to C<$result_cb> will
293     be C<undef> (if the error type was not 'continue') and the third argument will
294     be a Net::XMPP2::Node containg the IQ error stanza. And the fourth argument
295     will be a array reference with following contents:
296    
297     =over 4
298    
299     =item index 0: error type
300    
301     This will be one of: 'cancel', 'continue', 'modify', 'auth' and 'wait'.
302    
303     =item index 1: error condition element
304    
305     This might be undefined if other XMPP speakers don't play nice i guess.
306    
307     =item index 2: error text
308    
309     This will be the human readable form of the error which is maybe undef if
310     not supplied.
311    
312     =item index 3: error code
313    
314     If the error element had an 'code' attribute it will be put here,
315     the RFC says that this is for backward compatibility :)
316    
317     =back
318    
319     =cut
320    
321     sub send_iq {
322     my ($self, $type, $create_cb, $result_cb, %attrs) = @_;
323     my $id = $self->{iq_id}++;
324     $self->{iqs}->{$id} = $result_cb;
325     $self->{writer}->send_iq ($id, $type, $create_cb, %attrs);
326     }
327    
328     sub handle_iq {
329     my ($self, $node) = @_;
330    
331     if ($node->attr ('type') eq 'result') {
332     if (my $cb = $self->{iqs}->{$node->attr ('id')}) {
333     $cb->($node);
334     }
335     } elsif ($node->attr ('type') eq 'error') {
336     if (my $cb = $self->{iqs}->{$node->attr ('id')}) {
337    
338     my $error = $self->filter_error_stanza ($node);
339     $cb->(($error->[0] eq 'continue' ? $node : undef), $node, $error);
340     }
341     }
342     }
343    
344     sub filter_error_stanza {
345     my ($self, $node) = @_;
346     my $p = $self->{parser};
347     my @error;
348     my ($err) = $node->find_all ([qw/client error/]);
349     $error[0] = $err->attr ('type');
350     $error[3] = $err->attr ('code');
351     if ($err) {
352     if (my ($txt) = $err->find_all ([qw/stanzas text/])) {
353     $error[2] = $txt->text;
354     }
355     for my $er (
356     qw/bad-request conflict feature-not-implemented forbidden
357     gone internal-server-error item-not-found jid-malformed
358     not-acceptable not-allowed not-authorized payment-required
359     recipient-unavailable redirect registration-required
360     remote-server-not-found remote-server-timeout resource-constraint
361     service-unavailable subscription-required undefined-condition
362     unexpected-request/)
363     {
364     if (my ($el) = $err->find_all ([stanzas => $er])) {
365     $error[1] = $el;
366     last;
367     }
368     }
369     } else {
370     warn "no error element found in error stanza!";
371     }
372     return \@error
373     }
374    
375     sub handle_stream_features {
376     my ($self, $node) = @_;
377     my @mechs = $node->find_all ([qw/sasl mechanisms/], [qw/sasl mechanism/]);
378     my @bind = $node->find_all ([qw/bind bind/]);
379    
380     if (not ($self->{authenticated}) and @mechs) {
381     $self->{writer}->send_sasl_auth (
382     (join ' ', map { $_->text } @mechs),
383     $self->{username}, $self->{domain}, $self->{password}
384     );
385    
386     } elsif (@bind) {
387     $self->do_rebind ($self->{resource});
388     }
389     }
390    
391     sub handle_sasl_challenge {
392     my ($self, $node) = @_;
393     $self->{writer}->send_sasl_response ($node->text);
394     }
395    
396     sub handle_sasl_success {
397     my ($self, $node) = @_;
398     $self->{authenticated} = 1;
399     $self->{parser}->init;
400     $self->{writer}->init;
401     $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
402     }
403    
404     sub handle_error {
405     my ($self, $node) = @_;
406     my @txt = $node->find_all ([qw/stream text/]);
407     my $error;
408     for my $er (
409     qw/bad-format bad-namespace-prefix conflict connection-timeout host-gone
410     host-unknown improper-addressing internal-server-error invalid-from
411     invalid-id invalid-namespace invalid-xml not-authorized policy-violation
412     remote-connection-failed resource-constraint restricted-xml
413     see-other-host system-shutdown undefined-condition unsupported-stanza-type
414     unsupported-version xml-not-well-formed/)
415     {
416     for ($node->nodes) {
417     if ($node->eq (streams => $er)) {
418     $error = $_->name;
419     last
420     }
421     }
422     }
423     unless ($error) {
424     warn "got undefined error stanza, trying to find any undefined error...";
425     for ($node->nodes) {
426     if ($node->eq_ns ('streams')) {
427     $error = $node->name;
428     }
429     }
430     }
431     $self->event (stream_error => $error, (@txt ? $txt[0]->text : ''));
432     $self->{writer}->send_end_of_stream;
433     }
434    
435     =head2 do_rebind ($resource)
436    
437     In case you got a C<bind_error> event and want to retry
438     binding you can call this function to set a new C<$resource>
439     and retry binding.
440    
441     If it fails again you can call this again. Becareful not to
442     end up in a loop!
443    
444     If binding was successful the C<stream_ready> event will be generated.
445    
446     =cut
447    
448     sub do_rebind {
449     my ($self, $resource) = @_;
450     $self->{resource} = $resource;
451     $self->send_iq (
452     set =>
453     sub {
454     my ($w) = @_;
455     if ($self->{resource}) {
456     $w->startTag ([xmpp_ns ('bind'), 'bind']);
457     $w->startTag ([xmpp_ns ('bind'), 'resource']);
458     $w->characters ($self->{resource});
459     $w->endTag;
460     $w->endTag;
461     } else {
462     $w->emptyTag ([xmpp_ns ('bind'), 'bind'])
463     }
464     },
465     sub {
466     my ($ret_iq, $err_iq, $err) = @_;
467    
468     if ($err) {
469     my ($res) = $err_iq->find_all ([qw/bind bind/], [qw/bind resource/]);
470     $self->event (bind_error => $err->[0], ($res ? $res : $self->{resource}));
471    
472     } else {
473     my @jid = $ret_iq->find_all ([qw/bind bind/], [qw/bind jid/]);
474     my $jid = $jid[0]->text;
475     unless ($jid) { die "Got empty JID tag from server!\n" }
476     $self->{jid} = $jid;
477    
478     $self->event (stream_ready => $jid);
479     }
480     }
481     );
482     }
483    
484     =head2 jid
485    
486     After the stream has been bound to a resource the JID can be retrieved via this
487     method.
488    
489     =cut
490    
491     sub jid { $_[0]->{jid} }
492    
493     =head1 EVENTS
494    
495     These events can be registered on with C<reg_cb>:
496    
497     =over 4
498    
499     =item stream_features => $node
500    
501     This
502    
503     =item stream_ready => $jid
504    
505     This event is sent if the XML stream has been established (and
506     resources have been bound) and is ready for transmitting regular stanzas.
507    
508     C<$jid> is the bound jabber id.
509    
510     =item bind_error => $error_name, $resource
511    
512     This event is generated when the stream was unable to bind to
513     any or the in C<new> specified resource. C<$error_name>
514     may be 'bad-request', 'not-allowed' or 'conflict'.
515    
516     Node: this is untested, i couldn't get the server to send a bind error
517     to test this.
518    
519     =item connect => $host, $port
520    
521     This event is generated when a successful connect was performed to
522     the domain passed to C<new>.
523    
524     Note: C<$host> and C<$port> might be different from the domain you passed to
525     C<new> if C<connect> performed a SRV RR lookup.
526    
527     If this connection is lost a C<disconnect> will be generated with the same
528     C<$host> and C<$port>.
529    
530     =item disconnect => $host, $port, $message
531    
532     This event is generated when the connection was lost or another error
533     occured while writing or reading from it.
534    
535     C<$message> is a humand readable error message for the failure.
536     C<$host> and C<$port> were the host and port we were connected to.
537    
538     Note: C<$host> and C<$port> might be different from the domain you passed to
539     C<new> if C<connect> performed a SRV RR lookup.
540    
541     =back
542    
543     =head1 AUTHOR
544    
545     Robin Redeker, C<< <elmex at ta-sa.org> >>
546    
547     =head1 BUGS
548    
549     Please report any bugs or feature requests to
550     C<bug-net-xmpp2 at rt.cpan.org>, or through the web interface at
551     L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-XMPP2>.
552     I will be notified, and then you'll automatically be notified of progress on
553     your bug as I make changes.
554    
555     =head1 SUPPORT
556    
557     You can find documentation for this module with the perldoc command.
558    
559     perldoc Net::XMPP2
560    
561     You can also look for information at:
562    
563     =over 4
564    
565     =item * AnnoCPAN: Annotated CPAN documentation
566    
567     L<http://annocpan.org/dist/Net-XMPP2>
568    
569     =item * CPAN Ratings
570    
571     L<http://cpanratings.perl.org/d/Net-XMPP2>
572    
573     =item * RT: CPAN's request tracker
574    
575     L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-XMPP2>
576    
577     =item * Search CPAN
578    
579     L<http://search.cpan.org/dist/Net-XMPP2>
580    
581     =back
582    
583     =head1 ACKNOWLEDGEMENTS
584    
585     =head1 COPYRIGHT & LICENSE
586    
587     Copyright 2007 Robin Redeker, all rights reserved.
588    
589     This program is free software; you can redistribute it and/or modify it
590     under the same terms as Perl itself.
591    
592     =cut
593    
594     package Net::XMPP2::SimpleConnection;
595     use IO::Socket::INET;
596     use Fcntl;
597    
598     sub new {
599     my $this = shift;
600     my $class = ref($this) || $this;
601     my $self = { disconnect_cb => sub {}, @_ };
602     bless $self, $class;
603     return $self;
604     }
605    
606     sub connect {
607     my ($self, $host, $port) = @_;
608    
609     $self->{socket}
610     and return 1;
611    
612     my $sock = IO::Socket::INET->new (
613     PeerAddr => $host,
614     PeerPort => $port,
615     Proto => 'tcp',
616     Blocking => 1
617     );
618     return undef unless $sock;;
619    
620     $self->{socket} = $sock;
621     $self->{host} = $host;
622     $self->{port} = $port;
623    
624     my $flags = 0;
625     fcntl($sock, F_GETFL, $flags)
626     or die "Couldn't get flags for HANDLE : $!\n";
627     $flags |= O_NONBLOCK;
628     fcntl($sock, F_SETFL, $flags)
629     or die "Couldn't set flags for HANDLE: $!\n";
630    
631     binmode $sock, ":utf8";
632    
633     $self->{r} =
634     AnyEvent->io (poll => 'r', fh => $sock, cb => sub {
635     my $l = sysread $sock, my $data, 1024;
636    
637     $self->{read_buffer} .= $data;
638     $self->handle_data (\$self->{read_buffer});
639    
640     unless ($l) {
641     if (defined $l) {
642     $self->{disconnect_cb}->($host, $port, "EOF from server '$host:$port'");
643     delete $self->{r};
644     delete $self->{socket};
645     return;
646    
647     } else {
648     $self->{disconnect_cb}->($host, $port, "Error while reading from server '$host:$port': $!");
649     delete $self->{socket};
650     delete $self->{r};
651     return;
652     }
653     }
654     });
655     return 1;
656     }
657    
658     sub write_data {
659     my ($self, $data) = @_;
660     return unless $self->{r};
661    
662     my $cl = $self->{socket};
663     $self->{write_buffer} .= $data;
664    
665     unless ($self->{w}) {
666     $self->{w} =
667     AnyEvent->io (poll => 'w', fh => $cl, cb => sub {
668     if (my $data = $self->{write_buffer}) {
669     my $len = syswrite $cl, $data;
670     unless ($len) {
671     if (not defined $len) {
672     warn "error when writing data on $self->{host}:$self->{port}: $!";
673     return;
674     } else {
675     delete $self->{w};
676     }
677     }
678    
679     if ($len == length $self->{write_buffer}) {
680     delete $self->{w};
681     }
682    
683     $self->{write_buffer} = substr $self->{write_buffer}, $len;
684     }
685     });
686     }
687     }
688    
689     1; # End of Net::XMPP2