PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/src/perl/lib/OpenSRF/Transport/SlimJabber/XMPPReader.pm

https://gitlab.com/evergreen-bjwebb/opensrf-debian
Perl | 347 lines | 226 code | 79 blank | 42 comment | 29 complexity | 54ad8c5cc88260a53e3e4b755daf86c2 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
  1. package OpenSRF::Transport::SlimJabber::XMPPReader;
  2. use strict; use warnings;
  3. use XML::Parser;
  4. use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
  5. use Time::HiRes qw/time/;
  6. use OpenSRF::Transport::SlimJabber::XMPPMessage;
  7. use OpenSRF::Utils::Logger qw/$logger/;
  8. # -----------------------------------------------------------
  9. # Connect, disconnect, and authentication messsage templates
  10. # -----------------------------------------------------------
  11. use constant JABBER_CONNECT =>
  12. "<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";
  13. use constant JABBER_BASIC_AUTH =>
  14. "<iq id='123' type='set'><query xmlns='jabber:iq:auth'>" .
  15. "<username>%s</username><password>%s</password><resource>%s</resource></query></iq>";
  16. use constant JABBER_DISCONNECT => "</stream:stream>";
  17. # -----------------------------------------------------------
  18. # XMPP Stream states
  19. # -----------------------------------------------------------
  20. use constant DISCONNECTED => 1;
  21. use constant CONNECT_RECV => 2;
  22. use constant CONNECTED => 3;
  23. # -----------------------------------------------------------
  24. # XMPP Message states
  25. # -----------------------------------------------------------
  26. use constant IN_NOTHING => 1;
  27. use constant IN_BODY => 2;
  28. use constant IN_THREAD => 3;
  29. use constant IN_STATUS => 4;
  30. # -----------------------------------------------------------
  31. # Constructor, getter/setters
  32. # -----------------------------------------------------------
  33. sub new {
  34. my $class = shift;
  35. my $socket = shift;
  36. my $self = bless({}, $class);
  37. $self->{queue} = [];
  38. $self->{stream_state} = DISCONNECTED;
  39. $self->{xml_state} = IN_NOTHING;
  40. $self->socket($socket);
  41. my $p = new XML::Parser(Handlers => {
  42. Start => \&start_element,
  43. End => \&end_element,
  44. Char => \&characters,
  45. });
  46. $self->parser($p->parse_start); # create a push parser
  47. $self->parser->{_parent_} = $self;
  48. $self->{message} = OpenSRF::Transport::SlimJabber::XMPPMessage->new;
  49. return $self;
  50. }
  51. sub push_msg {
  52. my($self, $msg) = @_;
  53. push(@{$self->{queue}}, $msg) if $msg;
  54. }
  55. sub next_msg {
  56. my $self = shift;
  57. return shift @{$self->{queue}};
  58. }
  59. sub peek_msg {
  60. my $self = shift;
  61. return (@{$self->{queue}} > 0);
  62. }
  63. sub parser {
  64. my($self, $parser) = @_;
  65. $self->{parser} = $parser if $parser;
  66. return $self->{parser};
  67. }
  68. sub socket {
  69. my($self, $socket) = @_;
  70. $self->{socket} = $socket if $socket;
  71. return $self->{socket};
  72. }
  73. sub stream_state {
  74. my($self, $stream_state) = @_;
  75. $self->{stream_state} = $stream_state if $stream_state;
  76. return $self->{stream_state};
  77. }
  78. sub xml_state {
  79. my($self, $xml_state) = @_;
  80. $self->{xml_state} = $xml_state if $xml_state;
  81. return $self->{xml_state};
  82. }
  83. sub message {
  84. my($self, $message) = @_;
  85. $self->{message} = $message if $message;
  86. return $self->{message};
  87. }
  88. # -----------------------------------------------------------
  89. # Stream and connection handling methods
  90. # -----------------------------------------------------------
  91. sub connect {
  92. my($self, $domain, $username, $password, $resource) = @_;
  93. $self->send(sprintf(JABBER_CONNECT, $domain));
  94. $self->wait(10);
  95. unless($self->{stream_state} == CONNECT_RECV) {
  96. $logger->error("No initial XMPP response from server");
  97. return 0;
  98. }
  99. $self->send(sprintf(JABBER_BASIC_AUTH, $username, $password, $resource));
  100. $self->wait(10);
  101. unless($self->connected) {
  102. $logger->error('XMPP connect failed');
  103. return 0;
  104. }
  105. return 1;
  106. }
  107. sub disconnect {
  108. my $self = shift;
  109. return unless $self->socket;
  110. if($self->tcp_connected) {
  111. $self->send(JABBER_DISCONNECT);
  112. shutdown($self->socket, 2);
  113. }
  114. close($self->socket);
  115. }
  116. # -----------------------------------------------------------
  117. # returns true if this stream is connected to the server
  118. # -----------------------------------------------------------
  119. sub connected {
  120. my $self = shift;
  121. return ($self->tcp_connected and $self->{stream_state} == CONNECTED);
  122. }
  123. # -----------------------------------------------------------
  124. # returns true if the socket is connected
  125. # -----------------------------------------------------------
  126. sub tcp_connected {
  127. my $self = shift;
  128. return ($self->socket and $self->socket->connected);
  129. }
  130. # -----------------------------------------------------------
  131. # sends pre-formated XML
  132. # -----------------------------------------------------------
  133. sub send {
  134. my($self, $xml) = @_;
  135. $self->{socket}->print($xml);
  136. }
  137. # -----------------------------------------------------------
  138. # Puts a file handle into blocking mode
  139. # -----------------------------------------------------------
  140. sub set_block {
  141. my $fh = shift;
  142. my $flags = fcntl($fh, F_GETFL, 0);
  143. $flags &= ~O_NONBLOCK;
  144. fcntl($fh, F_SETFL, $flags);
  145. }
  146. # -----------------------------------------------------------
  147. # Puts a file handle into non-blocking mode
  148. # -----------------------------------------------------------
  149. sub set_nonblock {
  150. my $fh = shift;
  151. my $flags = fcntl($fh, F_GETFL, 0);
  152. fcntl($fh, F_SETFL, $flags | O_NONBLOCK);
  153. }
  154. sub wait {
  155. my($self, $timeout) = @_;
  156. return $self->next_msg if $self->peek_msg;
  157. $timeout ||= 0;
  158. $timeout = undef if $timeout < 0;
  159. my $socket = $self->{socket};
  160. set_block($socket);
  161. # build the select readset
  162. my $infile = '';
  163. vec($infile, $socket->fileno, 1) = 1;
  164. my $nfound = select($infile, undef, undef, $timeout);
  165. return undef if !$nfound or $nfound == -1;
  166. # now slurp the data off the socket
  167. my $buf;
  168. my $read_size = 1024;
  169. my $nonblock = 0;
  170. while(my $n = sysread($socket, $buf, $read_size)) {
  171. $self->{parser}->parse_more($buf) if $buf;
  172. if($n < $read_size or $self->peek_msg) {
  173. set_block($socket) if $nonblock;
  174. last;
  175. }
  176. set_nonblock($socket) unless $nonblock;
  177. $nonblock = 1;
  178. }
  179. return $self->next_msg;
  180. }
  181. # -----------------------------------------------------------
  182. # Waits up to timeout seconds for a fully-formed XMPP
  183. # message to arrive. If timeout is < 0, waits indefinitely
  184. # -----------------------------------------------------------
  185. sub wait_msg {
  186. my($self, $timeout) = @_;
  187. my $xml;
  188. $timeout = 0 unless defined $timeout;
  189. if($timeout < 0) {
  190. while(1) {
  191. return $xml if $xml = $self->wait($timeout);
  192. }
  193. } else {
  194. while($timeout >= 0) {
  195. my $start = time;
  196. return $xml if $xml = $self->wait($timeout);
  197. $timeout -= time - $start;
  198. }
  199. }
  200. return undef;
  201. }
  202. # -----------------------------------------------------------
  203. # SAX Handlers
  204. # -----------------------------------------------------------
  205. sub start_element {
  206. my($parser, $name, %attrs) = @_;
  207. my $self = $parser->{_parent_};
  208. if($name eq 'message') {
  209. my $msg = $self->{message};
  210. $msg->{to} = $attrs{'to'};
  211. $msg->{from} = $attrs{router_from} if $attrs{router_from};
  212. $msg->{from} = $attrs{from} unless $msg->{from};
  213. $msg->{osrf_xid} = $attrs{'osrf_xid'};
  214. $msg->{type} = $attrs{type};
  215. } elsif($name eq 'body') {
  216. $self->{xml_state} = IN_BODY;
  217. } elsif($name eq 'thread') {
  218. $self->{xml_state} = IN_THREAD;
  219. } elsif($name eq 'stream:stream') {
  220. $self->{stream_state} = CONNECT_RECV;
  221. } elsif($name eq 'iq') {
  222. if($attrs{type} and $attrs{type} eq 'result') {
  223. $self->{stream_state} = CONNECTED;
  224. }
  225. } elsif($name eq 'status') {
  226. $self->{xml_state } = IN_STATUS;
  227. } elsif($name eq 'stream:error') {
  228. $self->{stream_state} = DISCONNECTED;
  229. } elsif($name eq 'error') {
  230. $self->{message}->{err_type} = $attrs{'type'};
  231. $self->{message}->{err_code} = $attrs{'code'};
  232. }
  233. }
  234. sub characters {
  235. my($parser, $chars) = @_;
  236. my $self = $parser->{_parent_};
  237. my $state = $self->{xml_state};
  238. if($state == IN_BODY) {
  239. $self->{message}->{body} .= $chars;
  240. } elsif($state == IN_THREAD) {
  241. $self->{message}->{thread} .= $chars;
  242. } elsif($state == IN_STATUS) {
  243. $self->{message}->{status} .= $chars;
  244. }
  245. }
  246. sub end_element {
  247. my($parser, $name) = @_;
  248. my $self = $parser->{_parent_};
  249. $self->{xml_state} = IN_NOTHING;
  250. if($name eq 'message') {
  251. $self->push_msg($self->{message});
  252. $self->{message} = OpenSRF::Transport::SlimJabber::XMPPMessage->new;
  253. } elsif($name eq 'stream:stream') {
  254. $self->{stream_state} = DISCONNECTED;
  255. }
  256. }
  257. # read all the data on the jabber socket through the
  258. # parser and drop the resulting message
  259. sub flush_socket {
  260. my $self = shift;
  261. return 0 unless $self->connected;
  262. while ($self->wait(0)) {
  263. # TODO remove this log line
  264. $logger->info("flushing data from socket...");
  265. }
  266. return $self->connected;
  267. }
  268. 1;