| 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 |