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