1BEGIN {
2    $ENV{LOGLEVEL} ||= "WARN";
3    use DJabberd::Log;
4    DJabberd::Log->set_logger();
5}
6use strict;
7use DJabberd;
8use DJabberd::Authen::AllowedUsers;
9use DJabberd::Authen::StaticPassword;
10use DJabberd::TestSAXHandler;
11use DJabberd::RosterStorage::InMemoryOnly;
12use DJabberd::Util;
13use IO::Socket::UNIX;
14
15my $HAS_SASL;
16eval "use Authen::SASL 2.1402";
17unless ($@) {
18    require DJabberd::SASL::AuthenSASL;
19    $HAS_SASL = 1;
20}
21
22sub once_logged_in {
23    my $cb = shift;
24    my $sasl = shift;
25    my $server = Test::DJabberd::Server->new(id => 1);
26    $server->start;
27    my $pa = Test::DJabberd::Client->new(server => $server, name => "partya");
28    if ($sasl) {
29        $pa->sasl_login($sasl);
30    }
31    else {
32        $pa->login;
33    }
34    $cb->($pa);
35    $server->kill;
36}
37
38sub two_parties {
39    my $cb = shift;
40
41    if ($ENV{WILDFIRE_S2S}) {
42        two_parties_wildfire_to_local($cb);
43        return;
44    }
45
46    if ($ENV{WILDFIRE_TEST}) {
47        two_parties_one_wildfire($cb);
48        return;
49    }
50
51    two_parties_one_server($cb);
52    sleep 1;
53    two_parties_s2s($cb);
54    sleep 1;
55}
56
57sub two_parties_one_wildfire {
58    my $cb = shift;
59
60    my $wf = Test::DJabberd::Server->new(id => 1,
61                                         type => 'wildfire',
62                                         clientport => 5222,
63                                         serverport => 5269,
64                                         hostname => 's2.example.com',
65                                         connect_ip => '24.232.168.187',
66                                         not_local => 1,
67                                         );
68    $wf->start;
69    my $pa = Test::DJabberd::Client->new(server => $wf, name => "partya");
70    my $pb = Test::DJabberd::Client->new(server => $wf, name => "partyb");
71
72    $pa->create_fresh_account;
73    $pb->create_fresh_account;
74
75    $cb->($pa, $pb);
76
77}
78
79sub two_parties_wildfire_to_local {
80    my $cb = shift;
81
82    my $wf = Test::DJabberd::Server->new(id => 2,
83                                         type => 'wildfire',
84                                         clientport => 5222,
85                                         serverport => 5269,
86                                         hostname => 's2.example.com',
87                                         connect_ip => '24.232.168.187',
88                                         not_local => 1,
89                                         );
90    $wf->start;
91    my $pa = Test::DJabberd::Client->new(server => $wf, name => "partya");
92    $pa->create_fresh_account;
93
94    my $server1 = Test::DJabberd::Server->new(id => 1);
95    $server1->link_with($wf);
96    my $pb = Test::DJabberd::Client->new(server => $server1, name => "partyb");
97
98    $cb->($pa, $pb);
99}
100
101sub two_parties_one_server {
102    my $cb = shift;
103
104    my $server = Test::DJabberd::Server->new(id => 1);
105    $server->start;
106
107    my $pa = Test::DJabberd::Client->new(server => $server, name => "partya");
108    my $pb = Test::DJabberd::Client->new(server => $server, name => "partyb");
109    $cb->($pa, $pb);
110
111    $server->kill;
112}
113
114sub two_parties_s2s {
115    my $cb = shift;
116
117    my $server1 = Test::DJabberd::Server->new(id => 1);
118    my $server2 = Test::DJabberd::Server->new(id => 2);
119    $server1->link_with($server2);
120    $server2->link_with($server1);
121    $server1->start;
122    $server2->start;
123
124    my $pa = Test::DJabberd::Client->new(server => $server1, name => "partya");
125    my $pb = Test::DJabberd::Client->new(server => $server2, name => "partyb");
126    $cb->($pa, $pb);
127
128    $server1->kill;
129    $server2->kill;
130}
131
132sub two_parties_cluster {
133    my $cb = shift;
134
135    my $server1 = Test::DJabberd::Server->new(id => 1);
136    my $server2 = Test::DJabberd::Server->new(id => 2);
137    # TODO: configure these to know about each other.
138    $server1->start;
139    $server2->start;
140
141    my $pa = Test::DJabberd::Client->new(server => $server1, name => "partya");
142    my $pb = Test::DJabberd::Client->new(server => $server2, name => "partyb");
143    $cb->($pa, $pb);
144
145    $server1->kill;
146    $server2->kill;
147}
148
149sub test_responses {
150    my ($client, %map) = @_;
151    my $n = values %map;
152    # TODO: timeout on recv_xml_obj and die if don't get 'em all
153    my @stanzas;
154    my $verbose = ($ENV{LOGLEVEL} || "") eq "DEBUG";
155    for (1..$n) {
156        warn "Reading stanza $_/$n...\n" if $verbose;
157        push @stanzas, $client->recv_xml_obj;
158        warn "Got stanza: " . $stanzas[-1]->as_xml . "\n" if $verbose;
159    }
160
161    my %unmatched = %map;
162  STANZA:
163    foreach my $s (@stanzas) {
164        foreach my $k (keys %unmatched) {
165            my $tester = $map{$k};
166            my $okay = eval { $tester->($s, $s->as_xml); };
167            if ($okay) {
168                Test::More::pass("matched response '$k'");
169                delete $unmatched{$k};
170                next STANZA;
171            }
172        }
173        Carp::croak("Didn't match stanza: " . $s->as_xml);
174    }
175
176}
177
178package Test::DJabberd::Server;
179use strict;
180use overload
181    '""' => \&as_string;
182
183use Data::Dumper    qw[Dumper];
184local $Data::Dumper::Indent = 1;
185
186our $PLUGIN_CB;
187our $VHOST_CB;
188our @SUBDOMAINS;
189
190sub as_string {
191    my $self = shift;
192    return $self->hostname;
193}
194
195sub new {
196    my $class = shift;
197    my $self = bless {@_}, $class;
198
199    die 'ID required. Pass it as ->new(id => $id)' unless $self->{id};
200    return $self;
201}
202
203sub peeraddr {
204    my $self = shift;
205    return $self->{connect_ip} || ($self->{not_local} ? $self->{hostname} : "127.0.0.1");
206}
207
208sub serverport {
209    my $self = shift;
210    return $self->{serverport} || "1100$self->{id}";
211}
212
213sub clientport {
214    my $self = shift;
215    return $self->{clientport} || "1000$self->{id}";
216}
217
218sub adminport {
219    my $self = shift;
220    return $self->{adminport} || 0;
221}
222
223sub id {
224    my $self = shift;
225    return $self->{id};
226}
227
228sub hostname {
229    my $self = shift;
230    return $self->{hostname} || "s$self->{id}.example.com";
231}
232
233sub link_with {
234    my ($self, $other) = @_;
235    push @{$self->{peers}}, $other;
236}
237
238sub roster_name {
239    my $self = shift;
240    use FindBin qw($Bin);
241    return "$Bin/t-roster-$self->{id}.sqlite";
242}
243
244sub roster {
245    my $self = shift;
246    my $roster = $self->roster_name;
247
248    # We need to clear the cache so we can really unlink it, kind of ghetto
249    my $dbh = DBI->connect("dbi:SQLite:dbname=$roster","","", { RaiseError => 1, PrintError => 0, AutoCommit => 1 });
250    my $CachedKids_hashref = $dbh->{Driver}->{CachedKids};
251    %$CachedKids_hashref = () if $CachedKids_hashref;
252    unlink $roster;
253    return $roster;
254}
255
256sub standard_plugins {
257    my $self = shift;
258    my @sasl;
259    @sasl = ( DJabberd::SASL::AuthenSASL->new(
260                mechanisms => "LOGIN PLAIN DIGEST-MD5",
261                optional   => "yes",
262            )) if $HAS_SASL;
263    return [
264            DJabberd::Authen::AllowedUsers->new(policy => "deny",
265                                                allowedusers => [qw(partya partyb)]),
266            DJabberd::Authen::StaticPassword->new(password => "password"),
267            DJabberd::RosterStorage::InMemoryOnly->new(),
268            ($ENV{T_MUC_ENABLE} ? (DJabberd::Plugin::MUC->new(subdomain => 'conference')) : ()),
269            DJabberd::Delivery::Local->new,
270            DJabberd::Delivery::S2S->new,
271            @sasl
272            ];
273}
274
275sub std_plugins_sans_sasl {
276    my $self = shift;
277    return [ grep { ref($_) !~ /SASL/ } @{ $self->standard_plugins }  ];
278}
279
280sub start {
281    my $self = shift;
282    my $type = $self->{type} || "djabberd";
283
284    if ($type eq "djabberd") {
285        my $plugins = shift || ($PLUGIN_CB ? $PLUGIN_CB->($self) : $self->standard_plugins);
286        my $vhost = DJabberd::VHost->new(
287                                         server_name => $self->hostname,
288                                         s2s         => 1,
289                                         plugins     => $plugins,
290                                         );
291        my $server = DJabberd->new;
292        $server->set_config_unixdomainsocket($self->{unixdomainsocket}) if $self->{unixdomainsocket};
293
294        foreach my $peer (@{$self->{peers} || []}){
295            $server->set_fake_s2s_peer($peer->hostname => DJabberd::IPEndPoint->new($peer->peeraddr, $peer->serverport));
296            foreach my $subdomain (@SUBDOMAINS) {
297                $server->set_fake_s2s_peer($subdomain . '.' . $peer->hostname => DJabberd::IPEndPoint->new("127.0.0.1", $peer->serverport));
298            }
299        }
300
301        $VHOST_CB->($vhost) if $VHOST_CB;
302
303        $server->add_vhost($vhost);
304        $server->set_config_serverport($self->serverport);
305        $server->set_config_clientport($self->clientport);
306        $server->set_config_adminport($self->adminport) if $self->adminport;
307
308        if( my $server_callback = shift ){
309          $server_callback->($server);
310        }
311
312        my $childpid = fork;
313        if (!$childpid) {
314            unless ($ENV{TESTDEBUG}) {
315                ## no spurious output unless debugging
316                close(STDIN);
317                close(STDOUT);
318                close(STDERR);
319                open(STDIN,  "+>/dev/null");
320                open(STDOUT, "+>&STDIN");
321                open(STDERR, "+>&STDIN");
322            }
323            $server->run;
324            exit 0;
325        }
326
327        $self->{pid} = $childpid;
328    }
329
330    if ($type eq "wildfire") {
331        #...
332    }
333
334    return $self;
335}
336
337sub kill {
338    my $self = shift;
339    if ($self->{pid}) {
340        CORE::kill(9, $self->{pid});
341        my $pid = delete $self->{pid};
342        local $?; # we don't want to inherit the waited pid's exit status
343        waitpid $pid, 0;
344    }
345}
346
347sub DESTROY {
348    my $self = shift;
349    $self->kill;
350}
351
352package Test::DJabberd::Client;
353use MIME::Base64;
354use strict;
355
356use overload
357    '""' => \&as_string;
358
359use Data::Dumper    qw[Dumper];
360local $Data::Dumper::Indent = 1;
361
362sub resource {
363    return $_[0]{resource} ||= ($ENV{UNICODE_RESOURCE} ? "test\xe2\x80\x99s computer" : "testsuite_with_gibberish:'\"");
364}
365
366sub username {
367    my $self = shift;
368    return $self->{name};
369}
370
371sub password {
372    my $self = shift;
373    return $self->{password} || "password";
374}
375
376sub as_string {
377    my $self = shift;
378    return $self->username . '@' . $self->{server}->hostname;
379}
380
381sub server {
382    my $self = shift;
383    return $self->{server};
384}
385
386sub new {
387    my $class = shift;
388    my $self = bless {@_}, $class;
389    $self->{events} = [];
390    $self->{readbuf} = '';
391    die unless $self->{name};
392    return $self;
393}
394
395sub get_event {
396    my ($self, $timeout, $midstream) = @_;
397    $timeout ||= 10;
398
399    my $handler = DJabberd::TestSAXHandler->new($self->{events});
400    my $parser = DJabberd::XMLParser->new( Handler => $handler );
401    if ($midstream) {
402        $parser->parse_more("<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'>");
403        pop @{$self->{events}};
404    }
405
406    my $undef = sub {
407        my $why = shift;
408        $parser->finish_push;
409        #warn "Returning undef because: $why\n";
410        return undef;
411    };
412
413    #$| = 1;
414    #print "going to read...\n";
415
416    my $get_byte;
417    $get_byte = sub {
418        if (length $self->{readbuf}) {
419            my $byte = substr($self->{readbuf}, 0, 1, '');
420            return $byte;
421        }
422
423        my $byte;
424        my $rin = '';
425        vec($rin, fileno($self->{sock}), 1) = 1;
426        my $n = select($rin, undef, undef, $timeout)
427            or return $undef->("select timeout");
428
429        IO::Handle::blocking($self->{sock}, 0);
430        my $rv = sysread($self->{sock}, $self->{readbuf}, 4096);
431        IO::Handle::blocking($self->{sock}, 1);
432        if (!$rv) {
433            return $undef->("sysread no return");
434        }
435        $get_byte->();
436    };
437
438    while (! @{$self->{events}}) {
439        my $byte = $get_byte->();
440        return undef unless defined $byte;
441        $parser->parse_more($byte);
442    }
443    my $ev = shift @{$self->{events}};
444    #print "\ngot event: [$ev]\n";
445    #if (UNIVERSAL::isa($ev, "DJabberd::XMLElement")) {
446    #    print "  looks like: " . $ev->as_xml . "\n";
447    #}
448
449    $parser->finish_push;
450    $handler->{on_end_capture} = undef;
451
452    return $ev;
453}
454
455# if using $timeout, you're declaring that you can expect nothing and
456# undef is returned.  otherwise must return XML.  (or die after 10 seconds)
457sub recv_xml {
458    my ($self, $timeout) = @_;
459    my $ev = $self->get_event($timeout, 1);
460    return undef if $timeout && !$ev;
461    die "Expecting a DJabberd::XMLElement, got a $ev" unless UNIVERSAL::isa($ev, "DJabberd::XMLElement");
462    return $ev->as_xml;
463}
464
465# if using $timeout, you're declaring that you can expect nothing and
466# undef is returned.  otherwise must return XML.  (or die after 10 seconds)
467sub recv_xml_obj {
468    my ($self, $timeout) = @_;
469    my $ev = $self->get_event($timeout, 1);
470    die unless UNIVERSAL::isa($ev, "DJabberd::XMLElement");
471    return $ev;
472}
473
474sub get_stream_start {
475    my $self = shift;
476    my $ev = $self->get_event();
477    die unless $ev && $ev->isa("DJabberd::StreamStart");
478    return $ev;
479}
480
481sub send_xml {
482    my $self = shift;
483    my $xml  = shift;
484    $self->{sock}->print($xml);
485}
486
487sub create_fresh_account {
488    my $self = shift;
489    eval {
490        warn "trying to login for " . $self->username . " ...\n" if $ENV{TESTDEBUG};
491        if ($self->login) {
492            warn "  Logged in.\n" if $ENV{TESTDEBUG};
493            $self->send_xml(qq{<iq type='set' id='unreg1'>
494                                   <query xmlns='jabber:iq:register'>
495                                   <remove/>
496                                   </query>
497                                   </iq>});
498            my $res = $self->recv_xml;
499            die "Couldn't wipe our account: $res" unless $res =~ /type=.result./;
500            warn "  unregistered.\n" if $ENV{TESTDEBUG};
501        }
502    };
503    warn "Error logging in: [$@]" if $@ && $ENV{TESTDEBUG};
504
505    warn "Connecting...\n" if $ENV{TESTDEBUG};
506    $self->connect
507        or die "Couldn't connect to server";
508
509    warn "Connected, getting auth types..\n" if $ENV{TESTDEBUG};
510    $self->send_xml(qq{<iq type='get' id='reg1'>
511                             <query xmlns='jabber:iq:register'/>
512                             </iq>});
513
514    my $res = $self->recv_xml;
515    die "No in-band reg instructions: $res" unless $res =~ qr/<instructions>/;
516
517    my $user = $self->username;
518    my $pass = $self->password;
519
520    warn "registering ($user / $pass)...\n" if $ENV{TESTDEBUG};
521    $self->send_xml(qq{<iq type='set' id='reg1'>
522                             <query xmlns='jabber:iq:register'>
523                             <username>$user</username>
524                             <password>$pass</password>
525                             </query>
526                             </iq>});
527
528    $res = $self->recv_xml;
529    die "failed to reg account: $res" unless $res =~ qr/type=.result./;
530    warn "created account.\n" if $ENV{TESTDEBUG};
531    $self->disconnect;
532    return 1;
533}
534
535sub disconnect {
536    my $self = shift;
537    $self->{sock} = undef;
538}
539
540sub connect {
541    my $self = shift;
542
543    my $sock;
544    my $addr;
545    if ($addr = $self->{unixdomainsocket}) {
546        $sock = IO::Socket::UNIX->new(Peer => $addr);
547    } else {
548        $addr = join(':',
549                     $self->server->peeraddr,
550                     $self->server->clientport);
551        for (1..3) {
552            $sock = IO::Socket::INET->new(PeerAddr => $addr,
553                                          Timeout => 1);
554            last if $sock;
555            sleep 1;
556        }
557    }
558
559    $self->{sock} = $sock
560        or die "Cannot connect to server " . $self->server->id . " ($addr)";
561
562
563    $self->send_stream_start;
564    $self->{ss} = $self->get_stream_start();
565
566    my $features = $self->recv_xml;
567    warn "FEATURES: $features" if $ENV{TESTDEBUG};
568    die "no features" unless $features =~ /^<([^\:]+\:)?features\b/;
569    return 1;
570}
571
572sub send_stream_start {
573    my $self = shift;
574    my $sock = $self->{sock};
575    my $to = $self->server->hostname;
576    print $sock "
577   <stream:stream
578       xmlns:stream='http://etherx.jabber.org/streams'
579       xmlns='jabber:client' to='$to' version='1.0'>";
580}
581
582sub sasl_login {
583    my $self = shift;
584    my $sasl = shift;
585    my $res  = shift || '';
586    my $sec  = shift;
587
588    warn "connecting for sasl login..\n" if $ENV{TESTDEBUG};
589    $self->connect or die "Failed to connect";
590
591    warn ".. connected after sasl login.\n" if $ENV{TESTDEBUG};
592
593    my $ss = $self->{ss};
594    my $sock = $self->{sock};
595    my $to = $self->server->hostname;
596    my $conn = $sasl->client_new("xmpp", $to, $sec);
597
598    my $mechanism = $conn->mechanism;
599    my $init = $conn->client_start();
600    warn "sending conn auth...$init\n" if $ENV{TESTDEBUG};
601    $init = $init ? encode_base64($init, '') : "=";
602    print $sock "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='$mechanism'>$init</auth>";
603
604    my $got_success_already = 0;
605    while ($conn->need_step) {
606        my $challenge = $self->recv_xml;
607        warn "challenge response: [$challenge]\n" if $ENV{TESTDEBUG};
608        die "Didn't get expected response: $challenge" unless $challenge =~ /challenge|success\b/;
609
610        if ($challenge =~ s/^.*>(.+)<.*$/$1/sm) {
611            $challenge = decode_base64($challenge);
612            warn "decoded challenge: [$challenge]\n" if $ENV{TESTDEBUG};
613        }
614
615        my $response = $conn->client_step($challenge);
616        if ($conn->is_success) {
617            $got_success_already = 1;
618        }
619        else {
620            warn "sending conn response [$response]\n" if $ENV{TESTDEBUG};
621            $response = $response ? encode_base64($response, '') : "="; # dupe
622            print $sock "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>$response</response>";
623        }
624    }
625    if (my $error = $conn->error) {
626        die "error in SASL $error";
627    }
628
629    unless ($got_success_already) {
630        my $final = $self->recv_xml;
631        die "auth error $final" unless $final && $final =~ /success/;
632    }
633    $self->{ss} = undef;
634    delete $self->{ss};
635
636    $self->send_stream_start;
637    $self->get_stream_start;
638
639    my $features = $self->recv_xml;
640    warn "FEATURES: $features" if $ENV{TESTDEBUG};
641    die "no features" unless $features =~ /^<([^\:]+\:)?features\b/;
642    die "no bind"     unless $features =~ /bind\b/sm;
643    die "no session"  unless $features =~ /session\b/sm;
644
645    return $self->bind_resource($res);
646}
647
648sub bind_resource {
649    my $self = shift;
650    my $res  = shift;
651    my $sock = $self->{sock};
652
653    print $sock <<EOB;
654<iq type='set' id='purple81e4b57b'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>$res</resource></bind></iq>
655EOB
656    my $iq = $self->recv_xml_obj;
657    die "invalid bind response" unless $iq->element_name eq 'iq';
658    my $bind = $iq->first_element;
659    die "invalid bind response " unless $bind->element_name eq 'bind';
660    my $jid_el = $bind  ->first_element or die "no jid elt...";
661    my $jid    = $jid_el->first_child   or die "no jid...";
662    return DJabberd::JID->new($jid);
663}
664
665sub abort_sasl_login {
666    my $self = shift;
667    my $sasl = shift;
668    my $sec  = shift;
669
670    $self->connect or die "Failed to connect";
671    my $ss = $self->{ss};
672    my $sock = $self->{sock};
673    my $to = $self->server->hostname;
674    my $conn = $sasl->client_new("xmpp", $to, $sec);
675
676    my $mechanism = $conn->mechanism;
677    my $init = $conn->client_start();
678    $init = $init ? encode_base64($init, '') : "=";
679    print $sock "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='$mechanism'>$init</auth>";
680
681    my $challenge = $self->recv_xml;
682    print $sock "<abort xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>";
683    return $self->recv_xml;
684}
685
686sub login {
687    my $self = shift;
688    my $password = shift || $self->password;
689
690    warn "connecting for login..\n" if $ENV{TESTDEBUG};
691    $self->connect or die "Failed to connect";
692
693    warn ".. connected after login.\n" if $ENV{TESTDEBUG};
694
695    my $ss = $self->{ss};
696    my $sock = $self->{sock};
697    my $to = $self->server->hostname;
698
699    my $username = $self->{name};
700
701    warn "getting auth types...\n" if $ENV{TESTDEBUG};
702    print $sock "<iq type='get' id='auth1'>
703  <query xmlns='jabber:iq:auth'/>
704</iq>";
705
706    my $authreply = $self->recv_xml;
707    warn "auth reply for types: [$authreply]\n" if $ENV{TESTDEBUG};
708
709    die "didn't get reply" unless $authreply =~ /id=.auth1\b/;
710    my $response = "";
711    if ($authreply =~ /\bpassword\b/) {
712        $response = "<password>$password</password>";
713    } elsif ($authreply =~ /\bdigest\b/) {
714        use Digest::SHA1 qw(sha1_hex);
715        my $dig = lc(sha1_hex($ss->id . $password));
716        $response = "<digest>$dig</digest>";
717    } else {
718        die "can't do password nor digest auth: [$authreply]";
719    }
720
721    my $res = $self->resource;
722    print $sock "<iq type='set' id='auth2'>
723  <query xmlns='jabber:iq:auth'>
724    <username>$username</username>
725    $response
726    <resource>$res</resource>
727  </query>
728</iq>";
729
730    my $authreply2 = $self->recv_xml;
731    warn "auth reply post-login: [$authreply2]\n" if $ENV{TESTDEBUG};
732
733    die "no reply" unless $authreply2 =~ /id=.auth2\b/;
734    die "bad password" unless $authreply2 =~ /type=.result\b/;
735
736    $self->{ss} = undef;
737    delete $self->{ss};
738
739    return 1;
740}
741
742sub get_roster {
743    my $self = shift;
744    $self->{requested_roster}++;
745    $self->send_xml(qq{<iq type='get' id='rosterplz'><query xmlns='jabber:iq:roster'/></iq>});
746    my $xmlo = $self->recv_xml_obj;
747    die unless $xmlo->as_xml =~ /type=.result.+jabber:iq:roster/s;
748    return $xmlo;
749}
750
751sub initial_presence {
752    my $self = shift;
753    my $message = shift || 'Default Message';
754    $self->send_xml(qq{<presence><status>$message</status></presence>});
755    $self->{initial_presence}++;
756}
757
758# assumes no roster has been requested yet.
759# assumes no initial presence has been sent yet.
760sub subscribe_successfully {
761    my ($self, $other) = @_;
762
763    $self->send_xml(qq{<presence to='$other' type='subscribe' />});
764
765    $other->recv_xml =~ /<pre.+\btype=.subscribe\b/
766        or die "other party ($other) didn't get type='subscribe'\n";
767
768    $other->send_xml(qq{<presence to='$self' type='subscribed' />});
769
770    main::test_responses($self,
771                         "presence" => sub {
772                             my ($xo, $xml) = @_;
773                             return 0 if $xml =~ /\btype=/;
774                             return 0 unless $xml =~ /<presence\b/;
775                             return 1;
776                         },
777                         "presence2" => sub {
778                             my ($xo, $xml) = @_;
779                             return 0 if $xml =~ /\btype=/;
780                             return 0 unless $xml =~ /<presence\b/;
781                             return 1;
782                         },
783                         "presence subscribed" => sub {
784                             my ($xo, $xml) = @_;
785                             return 0 unless $xml =~ /\btype=.subscribed\b/;
786                             return 0 unless $xml =~ /\bfrom=.$other\b/;
787                             return 1;
788                         },
789                         );
790}
791
792# this code is a bit duplicated from above, but it deals
793# with initial presence and roster requested
794# it also doesn't ack it
795
796sub subscribe_to {
797    my ($self, $to) = @_;
798    my $from = $self;
799
800    $from->send_xml(qq{<presence to='$to' type='subscribe' />});
801
802    $from->{state}->{subscribe_to}->{$to}++;
803    my @test;
804
805    if ($from->{requested_roster}) {
806        push @test, "roster push" => sub {
807            my ($xo, $xml) = @_;
808            my $subscription = $from->{state}->{subscribed_from}->{$to} ? "from" : "none";
809            return 0 unless $xml =~ /jid=.$to./;
810            return 0 unless $xml =~ /\bsubscription=.$subscription\b/;
811            return 0 if !$from->{state}->{subscribed_from}->{$to} && $xml !~ /ask=.subscribe\b/;
812            return 1;
813        },
814    }
815
816
817    main::test_responses($from, @test) if(@test);
818    my $xml = $to->recv_xml_obj;
819
820    main::is($xml->attr("{}to"), $to->as_string, "to pb");
821    main::is($xml->attr("{}from"), $from->as_string, "from a");
822    main::is($xml->attr("{}type"), "subscribe", "type subscribe");
823    $to->{state}->{subscribe_from}->{$from}++;
824
825
826
827}
828
829sub accept_subscription_from {
830    my ($self, $from) = @_;
831
832    $self->send_xml(qq{<presence to='$from' type='subscribed' />});
833    $self->{state}->{subscribed_from}->{$from}++;
834
835    my @test;
836    if ($from->{requested_roster}) {
837        push @test,"roster push" => sub {
838                       my ($xo, $xml) = @_;
839                       warn "B) $xml";
840                       if ($from->{state}->{subscribed_from}->{$self}) {
841                           $xml =~ /\bsubscription=.from\b/ && $xml =~ /ask=.subscribe\b/;
842                       } else {
843                           $xml =~ /\bsubscription=.to\b/;
844                       }
845                   };
846        push @test,"roster push" => sub {
847                       my ($xo, $xml) = @_;
848                       if ($from->{state}->{subscribed_from}->{$self} && $self->{state}->{subscribed_from}->{$from}) {
849                           $xml =~ /\bsubscription=.both\b/;
850                       } else {
851                           $xml =~ /\bsubscription=.to\b/;
852                           1;
853                       }
854                   };
855
856    }
857
858
859
860    if ($from->{initial_presence} && $self->{initial_presence}) {
861        push @test, "presence of user" => sub {
862            my ($xo, $xml) = @_;
863            $xml =~ /Default Message/;
864        };
865        push @test, "presence of user2" => sub {
866            my ($xo, $xml) = @_;
867            $xml =~ /Default Message/;
868        };
869    }
870
871    if ($from->{initial_presence}) {
872        push @test, "presence subscribed" => sub {
873            my ($xo, $xml) = @_;
874            return 0 unless $xml =~ /\btype=.subscribed\b/;
875            return 0 unless $xml =~ /\bfrom=.$self\b/;
876            return 1;
877        };
878    }
879     main::test_responses($from, @test) if (@test);
880    $from->{state}->{subscribed_to}->{$self}++;
881    my $xml = $self->recv_xml;
882    main::like($xml, qr/to=.$self\b/, "to $self");
883    if ($from->{state}->{subscribed_from}->{$self} && $self->{state}->{subscribed_from}->{$from}) {
884        main::like($xml, qr/subscription=.both\b/, "both");
885    } else {
886        main::like($xml, qr/subscription=.from\b/, "from");
887    }
888}
8891;
890