From 60ef117be4b0ebceba94f8fe926f1314fe02721e Mon Sep 17 00:00:00 2001 From: kjellander Date: Wed, 4 Jan 2017 22:29:39 -0800 Subject: [PATCH] Revert of Remove webrtc/libjingle/{xmllite,xmpp} (patchset #1 id:1 of https://codereview.webrtc.org/2617443003/ ) Reason for revert: Breaks Chromium FYI bots. tommi, please let me land this (I forgot to run them). Original issue's description: > Remove webrtc/libjingle/{xmllite,xmpp} as it's dead code. > > These sources have now been imported into Chromium's > src/third_party/libjingle_xmpp. > > BUG=webrtc:5539 > NOTRY=True > > Review-Url: https://codereview.webrtc.org/2617443003 > Cr-Commit-Position: refs/heads/master@{#15910} > Committed: https://chromium.googlesource.com/external/webrtc/+/1670b1fe6bbc8f63b039782979beaeefa215faac TBR=tommi@webrtc.org # Skipping CQ checks because original CL landed less than 1 days ago. NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true BUG=webrtc:5539 Review-Url: https://codereview.webrtc.org/2618633003 Cr-Commit-Position: refs/heads/master@{#15911} --- webrtc/BUILD.gn | 3 + webrtc/build/gn_isolate_map.pyl | 4 + webrtc/libjingle/BUILD.gn | 59 + webrtc/libjingle/DEPS | 5 + webrtc/libjingle/OWNERS | 10 + webrtc/libjingle/xmllite/BUILD.gn | 51 + webrtc/libjingle/xmllite/OWNERS | 6 + webrtc/libjingle/xmllite/qname.cc | 78 ++ webrtc/libjingle/xmllite/qname.h | 83 ++ webrtc/libjingle/xmllite/qname_unittest.cc | 114 ++ webrtc/libjingle/xmllite/xmlbuilder.cc | 130 ++ webrtc/libjingle/xmllite/xmlbuilder.h | 61 + .../libjingle/xmllite/xmlbuilder_unittest.cc | 177 +++ webrtc/libjingle/xmllite/xmlconstants.cc | 25 + webrtc/libjingle/xmllite/xmlconstants.h | 30 + webrtc/libjingle/xmllite/xmlelement.cc | 496 ++++++++ webrtc/libjingle/xmllite/xmlelement.h | 233 ++++ .../libjingle/xmllite/xmlelement_unittest.cc | 258 ++++ webrtc/libjingle/xmllite/xmlnsstack.cc | 178 +++ webrtc/libjingle/xmllite/xmlnsstack.h | 45 + .../libjingle/xmllite/xmlnsstack_unittest.cc | 241 ++++ webrtc/libjingle/xmllite/xmlparser.cc | 261 ++++ webrtc/libjingle/xmllite/xmlparser.h | 103 ++ .../libjingle/xmllite/xmlparser_unittest.cc | 285 +++++ webrtc/libjingle/xmllite/xmlprinter.cc | 174 +++ webrtc/libjingle/xmllite/xmlprinter.h | 32 + .../libjingle/xmllite/xmlprinter_unittest.cc | 45 + webrtc/libjingle/xmpp/BUILD.gn | 154 +++ webrtc/libjingle/xmpp/OWNERS | 6 + webrtc/libjingle/xmpp/asyncsocket.h | 72 ++ webrtc/libjingle/xmpp/chatroommodule.h | 253 ++++ webrtc/libjingle/xmpp/chatroommoduleimpl.cc | 737 ++++++++++++ webrtc/libjingle/xmpp/constants.cc | 613 ++++++++++ webrtc/libjingle/xmpp/constants.h | 551 +++++++++ webrtc/libjingle/xmpp/discoitemsquerytask.cc | 61 + webrtc/libjingle/xmpp/discoitemsquerytask.h | 65 + webrtc/libjingle/xmpp/fakexmppclient.h | 107 ++ webrtc/libjingle/xmpp/hangoutpubsubclient.cc | 400 +++++++ webrtc/libjingle/xmpp/hangoutpubsubclient.h | 178 +++ .../xmpp/hangoutpubsubclient_unittest.cc | 754 ++++++++++++ webrtc/libjingle/xmpp/iqtask.cc | 69 ++ webrtc/libjingle/xmpp/iqtask.h | 49 + webrtc/libjingle/xmpp/jid.cc | 379 ++++++ webrtc/libjingle/xmpp/jid.h | 80 ++ webrtc/libjingle/xmpp/jid_unittest.cc | 122 ++ webrtc/libjingle/xmpp/module.h | 35 + webrtc/libjingle/xmpp/moduleimpl.cc | 48 + webrtc/libjingle/xmpp/moduleimpl.h | 76 ++ webrtc/libjingle/xmpp/mucroomconfigtask.cc | 73 ++ webrtc/libjingle/xmpp/mucroomconfigtask.h | 47 + .../xmpp/mucroomconfigtask_unittest.cc | 127 ++ webrtc/libjingle/xmpp/mucroomdiscoverytask.cc | 66 + webrtc/libjingle/xmpp/mucroomdiscoverytask.h | 41 + .../xmpp/mucroomdiscoverytask_unittest.cc | 145 +++ webrtc/libjingle/xmpp/mucroomlookuptask.cc | 158 +++ webrtc/libjingle/xmpp/mucroomlookuptask.h | 76 ++ .../xmpp/mucroomlookuptask_unittest.cc | 187 +++ .../xmpp/mucroomuniquehangoutidtask.cc | 51 + .../xmpp/mucroomuniquehangoutidtask.h | 38 + .../mucroomuniquehangoutidtask_unittest.cc | 99 ++ webrtc/libjingle/xmpp/pingtask.cc | 93 ++ webrtc/libjingle/xmpp/pingtask.h | 55 + webrtc/libjingle/xmpp/pingtask_unittest.cc | 101 ++ webrtc/libjingle/xmpp/plainsaslhandler.h | 64 + webrtc/libjingle/xmpp/presenceouttask.cc | 141 +++ webrtc/libjingle/xmpp/presenceouttask.h | 37 + webrtc/libjingle/xmpp/presencereceivetask.cc | 141 +++ webrtc/libjingle/xmpp/presencereceivetask.h | 56 + webrtc/libjingle/xmpp/presencestatus.cc | 45 + webrtc/libjingle/xmpp/presencestatus.h | 188 +++ webrtc/libjingle/xmpp/prexmppauth.h | 71 ++ webrtc/libjingle/xmpp/pubsub_task.cc | 201 ++++ webrtc/libjingle/xmpp/pubsub_task.h | 58 + webrtc/libjingle/xmpp/pubsubclient.cc | 129 ++ webrtc/libjingle/xmpp/pubsubclient.h | 111 ++ .../libjingle/xmpp/pubsubclient_unittest.cc | 279 +++++ webrtc/libjingle/xmpp/pubsubstateclient.cc | 25 + webrtc/libjingle/xmpp/pubsubstateclient.h | 271 +++++ webrtc/libjingle/xmpp/pubsubtasks.cc | 204 ++++ webrtc/libjingle/xmpp/pubsubtasks.h | 114 ++ webrtc/libjingle/xmpp/pubsubtasks_unittest.cc | 281 +++++ webrtc/libjingle/xmpp/receivetask.cc | 34 + webrtc/libjingle/xmpp/receivetask.h | 41 + webrtc/libjingle/xmpp/rostermodule.h | 331 +++++ .../libjingle/xmpp/rostermodule_unittest.cc | 832 +++++++++++++ webrtc/libjingle/xmpp/rostermoduleimpl.cc | 1064 +++++++++++++++++ webrtc/libjingle/xmpp/rostermoduleimpl.h | 287 +++++ webrtc/libjingle/xmpp/saslcookiemechanism.h | 69 ++ webrtc/libjingle/xmpp/saslhandler.h | 42 + webrtc/libjingle/xmpp/saslmechanism.cc | 55 + webrtc/libjingle/xmpp/saslmechanism.h | 57 + webrtc/libjingle/xmpp/saslplainmechanism.h | 48 + webrtc/libjingle/xmpp/util_unittest.cc | 109 ++ webrtc/libjingle/xmpp/util_unittest.h | 58 + webrtc/libjingle/xmpp/xmppauth.cc | 88 ++ webrtc/libjingle/xmpp/xmppauth.h | 61 + webrtc/libjingle/xmpp/xmppclient.cc | 423 +++++++ webrtc/libjingle/xmpp/xmppclient.h | 149 +++ webrtc/libjingle/xmpp/xmppclientsettings.h | 111 ++ webrtc/libjingle/xmpp/xmppengine.h | 332 +++++ webrtc/libjingle/xmpp/xmppengine_unittest.cc | 327 +++++ webrtc/libjingle/xmpp/xmppengineimpl.cc | 446 +++++++ webrtc/libjingle/xmpp/xmppengineimpl.h | 267 +++++ webrtc/libjingle/xmpp/xmppengineimpl_iq.cc | 260 ++++ webrtc/libjingle/xmpp/xmpplogintask.cc | 380 ++++++ webrtc/libjingle/xmpp/xmpplogintask.h | 87 ++ .../libjingle/xmpp/xmpplogintask_unittest.cc | 637 ++++++++++ webrtc/libjingle/xmpp/xmpppump.cc | 67 ++ webrtc/libjingle/xmpp/xmpppump.h | 62 + webrtc/libjingle/xmpp/xmppsocket.cc | 246 ++++ webrtc/libjingle/xmpp/xmppsocket.h | 72 ++ webrtc/libjingle/xmpp/xmppstanzaparser.cc | 89 ++ webrtc/libjingle/xmpp/xmppstanzaparser.h | 80 ++ .../xmpp/xmppstanzaparser_unittest.cc | 175 +++ webrtc/libjingle/xmpp/xmpptask.cc | 158 +++ webrtc/libjingle/xmpp/xmpptask.h | 175 +++ webrtc/libjingle/xmpp/xmppthread.cc | 69 ++ webrtc/libjingle/xmpp/xmppthread.h | 45 + 118 files changed, 19602 insertions(+) create mode 100644 webrtc/libjingle/BUILD.gn create mode 100644 webrtc/libjingle/DEPS create mode 100644 webrtc/libjingle/OWNERS create mode 100644 webrtc/libjingle/xmllite/BUILD.gn create mode 100644 webrtc/libjingle/xmllite/OWNERS create mode 100644 webrtc/libjingle/xmllite/qname.cc create mode 100644 webrtc/libjingle/xmllite/qname.h create mode 100644 webrtc/libjingle/xmllite/qname_unittest.cc create mode 100644 webrtc/libjingle/xmllite/xmlbuilder.cc create mode 100644 webrtc/libjingle/xmllite/xmlbuilder.h create mode 100644 webrtc/libjingle/xmllite/xmlbuilder_unittest.cc create mode 100644 webrtc/libjingle/xmllite/xmlconstants.cc create mode 100644 webrtc/libjingle/xmllite/xmlconstants.h create mode 100644 webrtc/libjingle/xmllite/xmlelement.cc create mode 100644 webrtc/libjingle/xmllite/xmlelement.h create mode 100644 webrtc/libjingle/xmllite/xmlelement_unittest.cc create mode 100644 webrtc/libjingle/xmllite/xmlnsstack.cc create mode 100644 webrtc/libjingle/xmllite/xmlnsstack.h create mode 100644 webrtc/libjingle/xmllite/xmlnsstack_unittest.cc create mode 100644 webrtc/libjingle/xmllite/xmlparser.cc create mode 100644 webrtc/libjingle/xmllite/xmlparser.h create mode 100644 webrtc/libjingle/xmllite/xmlparser_unittest.cc create mode 100644 webrtc/libjingle/xmllite/xmlprinter.cc create mode 100644 webrtc/libjingle/xmllite/xmlprinter.h create mode 100644 webrtc/libjingle/xmllite/xmlprinter_unittest.cc create mode 100644 webrtc/libjingle/xmpp/BUILD.gn create mode 100644 webrtc/libjingle/xmpp/OWNERS create mode 100644 webrtc/libjingle/xmpp/asyncsocket.h create mode 100644 webrtc/libjingle/xmpp/chatroommodule.h create mode 100644 webrtc/libjingle/xmpp/chatroommoduleimpl.cc create mode 100644 webrtc/libjingle/xmpp/constants.cc create mode 100644 webrtc/libjingle/xmpp/constants.h create mode 100644 webrtc/libjingle/xmpp/discoitemsquerytask.cc create mode 100644 webrtc/libjingle/xmpp/discoitemsquerytask.h create mode 100644 webrtc/libjingle/xmpp/fakexmppclient.h create mode 100644 webrtc/libjingle/xmpp/hangoutpubsubclient.cc create mode 100644 webrtc/libjingle/xmpp/hangoutpubsubclient.h create mode 100644 webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc create mode 100644 webrtc/libjingle/xmpp/iqtask.cc create mode 100644 webrtc/libjingle/xmpp/iqtask.h create mode 100644 webrtc/libjingle/xmpp/jid.cc create mode 100644 webrtc/libjingle/xmpp/jid.h create mode 100644 webrtc/libjingle/xmpp/jid_unittest.cc create mode 100644 webrtc/libjingle/xmpp/module.h create mode 100644 webrtc/libjingle/xmpp/moduleimpl.cc create mode 100644 webrtc/libjingle/xmpp/moduleimpl.h create mode 100644 webrtc/libjingle/xmpp/mucroomconfigtask.cc create mode 100644 webrtc/libjingle/xmpp/mucroomconfigtask.h create mode 100644 webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/mucroomdiscoverytask.cc create mode 100644 webrtc/libjingle/xmpp/mucroomdiscoverytask.h create mode 100644 webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/mucroomlookuptask.cc create mode 100644 webrtc/libjingle/xmpp/mucroomlookuptask.h create mode 100644 webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc create mode 100644 webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h create mode 100644 webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/pingtask.cc create mode 100644 webrtc/libjingle/xmpp/pingtask.h create mode 100644 webrtc/libjingle/xmpp/pingtask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/plainsaslhandler.h create mode 100644 webrtc/libjingle/xmpp/presenceouttask.cc create mode 100644 webrtc/libjingle/xmpp/presenceouttask.h create mode 100644 webrtc/libjingle/xmpp/presencereceivetask.cc create mode 100644 webrtc/libjingle/xmpp/presencereceivetask.h create mode 100644 webrtc/libjingle/xmpp/presencestatus.cc create mode 100644 webrtc/libjingle/xmpp/presencestatus.h create mode 100644 webrtc/libjingle/xmpp/prexmppauth.h create mode 100644 webrtc/libjingle/xmpp/pubsub_task.cc create mode 100644 webrtc/libjingle/xmpp/pubsub_task.h create mode 100644 webrtc/libjingle/xmpp/pubsubclient.cc create mode 100644 webrtc/libjingle/xmpp/pubsubclient.h create mode 100644 webrtc/libjingle/xmpp/pubsubclient_unittest.cc create mode 100644 webrtc/libjingle/xmpp/pubsubstateclient.cc create mode 100644 webrtc/libjingle/xmpp/pubsubstateclient.h create mode 100644 webrtc/libjingle/xmpp/pubsubtasks.cc create mode 100644 webrtc/libjingle/xmpp/pubsubtasks.h create mode 100644 webrtc/libjingle/xmpp/pubsubtasks_unittest.cc create mode 100644 webrtc/libjingle/xmpp/receivetask.cc create mode 100644 webrtc/libjingle/xmpp/receivetask.h create mode 100644 webrtc/libjingle/xmpp/rostermodule.h create mode 100644 webrtc/libjingle/xmpp/rostermodule_unittest.cc create mode 100644 webrtc/libjingle/xmpp/rostermoduleimpl.cc create mode 100644 webrtc/libjingle/xmpp/rostermoduleimpl.h create mode 100644 webrtc/libjingle/xmpp/saslcookiemechanism.h create mode 100644 webrtc/libjingle/xmpp/saslhandler.h create mode 100644 webrtc/libjingle/xmpp/saslmechanism.cc create mode 100644 webrtc/libjingle/xmpp/saslmechanism.h create mode 100644 webrtc/libjingle/xmpp/saslplainmechanism.h create mode 100644 webrtc/libjingle/xmpp/util_unittest.cc create mode 100644 webrtc/libjingle/xmpp/util_unittest.h create mode 100644 webrtc/libjingle/xmpp/xmppauth.cc create mode 100644 webrtc/libjingle/xmpp/xmppauth.h create mode 100644 webrtc/libjingle/xmpp/xmppclient.cc create mode 100644 webrtc/libjingle/xmpp/xmppclient.h create mode 100644 webrtc/libjingle/xmpp/xmppclientsettings.h create mode 100644 webrtc/libjingle/xmpp/xmppengine.h create mode 100644 webrtc/libjingle/xmpp/xmppengine_unittest.cc create mode 100644 webrtc/libjingle/xmpp/xmppengineimpl.cc create mode 100644 webrtc/libjingle/xmpp/xmppengineimpl.h create mode 100644 webrtc/libjingle/xmpp/xmppengineimpl_iq.cc create mode 100644 webrtc/libjingle/xmpp/xmpplogintask.cc create mode 100644 webrtc/libjingle/xmpp/xmpplogintask.h create mode 100644 webrtc/libjingle/xmpp/xmpplogintask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/xmpppump.cc create mode 100644 webrtc/libjingle/xmpp/xmpppump.h create mode 100644 webrtc/libjingle/xmpp/xmppsocket.cc create mode 100644 webrtc/libjingle/xmpp/xmppsocket.h create mode 100644 webrtc/libjingle/xmpp/xmppstanzaparser.cc create mode 100644 webrtc/libjingle/xmpp/xmppstanzaparser.h create mode 100644 webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc create mode 100644 webrtc/libjingle/xmpp/xmpptask.cc create mode 100644 webrtc/libjingle/xmpp/xmpptask.h create mode 100644 webrtc/libjingle/xmpp/xmppthread.cc create mode 100644 webrtc/libjingle/xmpp/xmppthread.h diff --git a/webrtc/BUILD.gn b/webrtc/BUILD.gn index e3cb915064..e568fc7c7e 100644 --- a/webrtc/BUILD.gn +++ b/webrtc/BUILD.gn @@ -253,6 +253,8 @@ if (!build_with_chromium) { "call", "common_audio", "common_video", + "libjingle/xmllite", + "libjingle/xmpp", "logging", "media", "modules", @@ -289,6 +291,7 @@ if (!build_with_chromium) { "api:peerconnection_unittests", "common_audio:common_audio_unittests", "common_video:common_video_unittests", + "libjingle:xmllite_xmpp_unittests", "media:rtc_media_unittests", "modules:modules_tests", "modules:modules_unittests", diff --git a/webrtc/build/gn_isolate_map.pyl b/webrtc/build/gn_isolate_map.pyl index ed56777af0..95b9d8e9ea 100644 --- a/webrtc/build/gn_isolate_map.pyl +++ b/webrtc/build/gn_isolate_map.pyl @@ -95,4 +95,8 @@ "label": "//webrtc:webrtc_nonparallel_tests", "type": "non_parallel_console_test_launcher", }, + "xmllite_xmpp_unittests": { + "label": "//webrtc/libjingle:xmllite_xmpp_unittests", + "type": "console_test_launcher", + } } diff --git a/webrtc/libjingle/BUILD.gn b/webrtc/libjingle/BUILD.gn new file mode 100644 index 0000000000..0cf2aa6f24 --- /dev/null +++ b/webrtc/libjingle/BUILD.gn @@ -0,0 +1,59 @@ +# Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +import("../build/webrtc.gni") + +group("libjingle") { + public_deps = [ + "xmllite", + "xmpp", + ] +} + +if (rtc_include_tests) { + rtc_test("xmllite_xmpp_unittests") { + configs += [ "..:rtc_unittests_config" ] + + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin. + # See http://code.google.com/p/webrtc/issues/detail?id=163 for details. + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + + deps = [ + "../base:rtc_base_tests_utils", + "xmllite", + "xmpp", + "//testing/gtest", + ] + + sources = [ + "xmllite/qname_unittest.cc", + "xmllite/xmlbuilder_unittest.cc", + "xmllite/xmlelement_unittest.cc", + "xmllite/xmlnsstack_unittest.cc", + "xmllite/xmlparser_unittest.cc", + "xmllite/xmlprinter_unittest.cc", + "xmpp/fakexmppclient.h", + "xmpp/hangoutpubsubclient_unittest.cc", + "xmpp/jid_unittest.cc", + "xmpp/mucroomconfigtask_unittest.cc", + "xmpp/mucroomdiscoverytask_unittest.cc", + "xmpp/mucroomlookuptask_unittest.cc", + "xmpp/mucroomuniquehangoutidtask_unittest.cc", + "xmpp/pingtask_unittest.cc", + "xmpp/pubsubclient_unittest.cc", + "xmpp/pubsubtasks_unittest.cc", + "xmpp/util_unittest.cc", + "xmpp/util_unittest.h", + "xmpp/xmppengine_unittest.cc", + "xmpp/xmpplogintask_unittest.cc", + "xmpp/xmppstanzaparser_unittest.cc", + ] + } +} diff --git a/webrtc/libjingle/DEPS b/webrtc/libjingle/DEPS new file mode 100644 index 0000000000..7f492770e0 --- /dev/null +++ b/webrtc/libjingle/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+third_party/expat", + "+webrtc/base", + "+webrtc/p2p", +] diff --git a/webrtc/libjingle/OWNERS b/webrtc/libjingle/OWNERS new file mode 100644 index 0000000000..3ba7b66d28 --- /dev/null +++ b/webrtc/libjingle/OWNERS @@ -0,0 +1,10 @@ +henrika@webrtc.org +henrikg@webrtc.org +hta@webrtc.org +jiayl@webrtc.org +juberti@webrtc.org +mflodman@webrtc.org +perkj@webrtc.org +pthatcher@webrtc.org +sergeyu@chromium.org +tommi@webrtc.org diff --git a/webrtc/libjingle/xmllite/BUILD.gn b/webrtc/libjingle/xmllite/BUILD.gn new file mode 100644 index 0000000000..496d452bd8 --- /dev/null +++ b/webrtc/libjingle/xmllite/BUILD.gn @@ -0,0 +1,51 @@ +# Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +import("../../build/webrtc.gni") + +group("xmllite") { + public_deps = [ + ":rtc_xmllite", + ] +} + +rtc_static_library("rtc_xmllite") { + sources = [ + "qname.cc", + "qname.h", + "xmlbuilder.cc", + "xmlbuilder.h", + "xmlconstants.cc", + "xmlconstants.h", + "xmlelement.cc", + "xmlelement.h", + "xmlnsstack.cc", + "xmlnsstack.h", + "xmlparser.cc", + "xmlparser.h", + "xmlprinter.cc", + "xmlprinter.h", + ] + + deps = [ + "../../base:rtc_base", + ] + + if (rtc_build_expat) { + deps += [ "//third_party/expat" ] + public_deps = [ + "//third_party/expat", + ] + } + + if (!build_with_chromium && is_clang) { + # Suppress warnings from Chrome's Clang plugins. + # See http://code.google.com/p/webrtc/issues/detail?id=163 for details. + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } +} diff --git a/webrtc/libjingle/xmllite/OWNERS b/webrtc/libjingle/xmllite/OWNERS new file mode 100644 index 0000000000..6d6e52a6ae --- /dev/null +++ b/webrtc/libjingle/xmllite/OWNERS @@ -0,0 +1,6 @@ +# See ../OWNERS for more owners. + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gn=* +per-file *.gni=* diff --git a/webrtc/libjingle/xmllite/qname.cc b/webrtc/libjingle/xmllite/qname.cc new file mode 100644 index 0000000000..6abde32186 --- /dev/null +++ b/webrtc/libjingle/xmllite/qname.cc @@ -0,0 +1,78 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/qname.h" + +namespace buzz { + +QName::QName() { +} + +QName::QName(const QName& qname) + : namespace_(qname.namespace_), + local_part_(qname.local_part_) { +} + +QName::QName(const StaticQName& const_value) + : namespace_(const_value.ns), + local_part_(const_value.local) { +} + +QName::QName(const std::string& ns, const std::string& local) + : namespace_(ns), + local_part_(local) { +} + +QName::QName(const std::string& merged_or_local) { + size_t i = merged_or_local.rfind(':'); + if (i == std::string::npos) { + local_part_ = merged_or_local; + } else { + namespace_ = merged_or_local.substr(0, i); + local_part_ = merged_or_local.substr(i + 1); + } +} + +QName::~QName() { +} + +std::string QName::Merged() const { + if (namespace_[0] == '\0') + return local_part_; + + std::string result; + result.reserve(namespace_.length() + 1 + local_part_.length()); + result += namespace_; + result += ':'; + result += local_part_; + return result; +} + +bool QName::IsEmpty() const { + return namespace_.empty() && local_part_.empty(); +} + +int QName::Compare(const StaticQName& other) const { + int result = local_part_.compare(other.local); + if (result != 0) + return result; + + return namespace_.compare(other.ns); +} + +int QName::Compare(const QName& other) const { + int result = local_part_.compare(other.local_part_); + if (result != 0) + return result; + + return namespace_.compare(other.namespace_); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmllite/qname.h b/webrtc/libjingle/xmllite/qname.h new file mode 100644 index 0000000000..1e772d3d67 --- /dev/null +++ b/webrtc/libjingle/xmllite/qname.h @@ -0,0 +1,83 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_QNAME_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_QNAME_H_ + +#include + +namespace buzz { + +class QName; + +// StaticQName is used to represend constant quailified names. They +// can be initialized statically and don't need intializers code, e.g. +// const StaticQName QN_FOO = { "foo_namespace", "foo" }; +// +// Beside this use case, QName should be used everywhere +// else. StaticQName instances are implicitly converted to QName +// objects. +struct StaticQName { + const char* const ns; + const char* const local; + + bool operator==(const QName& other) const; + bool operator!=(const QName& other) const; +}; + +class QName { + public: + QName(); + QName(const QName& qname); + QName(const StaticQName& const_value); + QName(const std::string& ns, const std::string& local); + explicit QName(const std::string& merged_or_local); + ~QName(); + + const std::string& Namespace() const { return namespace_; } + const std::string& LocalPart() const { return local_part_; } + std::string Merged() const; + bool IsEmpty() const; + + int Compare(const StaticQName& other) const; + int Compare(const QName& other) const; + + bool operator==(const StaticQName& other) const { + return Compare(other) == 0; + } + bool operator==(const QName& other) const { + return Compare(other) == 0; + } + bool operator!=(const StaticQName& other) const { + return Compare(other) != 0; + } + bool operator!=(const QName& other) const { + return Compare(other) != 0; + } + bool operator<(const QName& other) const { + return Compare(other) < 0; + } + + private: + std::string namespace_; + std::string local_part_; +}; + +inline bool StaticQName::operator==(const QName& other) const { + return other.Compare(*this) == 0; +} + +inline bool StaticQName::operator!=(const QName& other) const { + return other.Compare(*this) != 0; +} + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMLLITE_QNAME_H_ diff --git a/webrtc/libjingle/xmllite/qname_unittest.cc b/webrtc/libjingle/xmllite/qname_unittest.cc new file mode 100644 index 0000000000..2a129b883a --- /dev/null +++ b/webrtc/libjingle/xmllite/qname_unittest.cc @@ -0,0 +1,114 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/base/gunit.h" + +using buzz::StaticQName; +using buzz::QName; + +TEST(QNameTest, TestTrivial) { + QName name("test"); + EXPECT_EQ(name.LocalPart(), "test"); + EXPECT_EQ(name.Namespace(), ""); +} + +TEST(QNameTest, TestSplit) { + QName name("a:test"); + EXPECT_EQ(name.LocalPart(), "test"); + EXPECT_EQ(name.Namespace(), "a"); + QName name2("a-very:long:namespace:test-this"); + EXPECT_EQ(name2.LocalPart(), "test-this"); + EXPECT_EQ(name2.Namespace(), "a-very:long:namespace"); +} + +TEST(QNameTest, TestMerge) { + QName name("a", "test"); + EXPECT_EQ(name.LocalPart(), "test"); + EXPECT_EQ(name.Namespace(), "a"); + EXPECT_EQ(name.Merged(), "a:test"); + QName name2("a-very:long:namespace", "test-this"); + EXPECT_EQ(name2.LocalPart(), "test-this"); + EXPECT_EQ(name2.Namespace(), "a-very:long:namespace"); + EXPECT_EQ(name2.Merged(), "a-very:long:namespace:test-this"); +} + +TEST(QNameTest, TestAssignment) { + QName name("a", "test"); + // copy constructor + QName namecopy(name); + EXPECT_EQ(namecopy.LocalPart(), "test"); + EXPECT_EQ(namecopy.Namespace(), "a"); + QName nameassigned(""); + nameassigned = name; + EXPECT_EQ(nameassigned.LocalPart(), "test"); + EXPECT_EQ(nameassigned.Namespace(), "a"); +} + +TEST(QNameTest, TestConstAssignment) { + StaticQName name = { "a", "test" }; + QName namecopy(name); + EXPECT_EQ(namecopy.LocalPart(), "test"); + EXPECT_EQ(namecopy.Namespace(), "a"); + QName nameassigned(""); + nameassigned = name; + EXPECT_EQ(nameassigned.LocalPart(), "test"); + EXPECT_EQ(nameassigned.Namespace(), "a"); +} + +TEST(QNameTest, TestEquality) { + QName name("a-very:long:namespace:test-this"); + QName name2("a-very:long:namespace", "test-this"); + QName name3("a-very:long:namespaxe", "test-this"); + EXPECT_TRUE(name == name2); + EXPECT_FALSE(name == name3); +} + +TEST(QNameTest, TestCompare) { + QName name("a"); + QName name2("nsa", "a"); + QName name3("nsa", "b"); + QName name4("nsb", "b"); + + EXPECT_TRUE(name < name2); + EXPECT_FALSE(name2 < name); + + EXPECT_FALSE(name2 < name2); + + EXPECT_TRUE(name2 < name3); + EXPECT_FALSE(name3 < name2); + + EXPECT_TRUE(name3 < name4); + EXPECT_FALSE(name4 < name3); +} + +TEST(QNameTest, TestStaticQName) { + const StaticQName const_name1 = { "namespace", "local-name1" }; + const StaticQName const_name2 = { "namespace", "local-name2" }; + const QName name("namespace", "local-name1"); + const QName name1 = const_name1; + const QName name2 = const_name2; + + EXPECT_TRUE(name == const_name1); + EXPECT_TRUE(const_name1 == name); + EXPECT_FALSE(name != const_name1); + EXPECT_FALSE(const_name1 != name); + + EXPECT_TRUE(name == name1); + EXPECT_TRUE(name1 == name); + EXPECT_FALSE(name != name1); + EXPECT_FALSE(name1 != name); + + EXPECT_FALSE(name == name2); + EXPECT_FALSE(name2 == name); + EXPECT_TRUE(name != name2); + EXPECT_TRUE(name2 != name); +} diff --git a/webrtc/libjingle/xmllite/xmlbuilder.cc b/webrtc/libjingle/xmllite/xmlbuilder.cc new file mode 100644 index 0000000000..9d982c3334 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlbuilder.cc @@ -0,0 +1,130 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlbuilder.h" + +#include +#include +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/base/common.h" + +namespace buzz { + +XmlBuilder::XmlBuilder() : + pelCurrent_(NULL), + pelRoot_(), + pvParents_(new std::vector()) { +} + +void +XmlBuilder::Reset() { + pelRoot_.reset(); + pelCurrent_ = NULL; + pvParents_->clear(); +} + +XmlElement * +XmlBuilder::BuildElement(XmlParseContext * pctx, + const char * name, const char ** atts) { + QName tagName(pctx->ResolveQName(name, false)); + if (tagName.IsEmpty()) + return NULL; + + XmlElement * pelNew = new XmlElement(tagName); + + if (!*atts) + return pelNew; + + std::set seenNonlocalAtts; + + while (*atts) { + QName attName(pctx->ResolveQName(*atts, true)); + if (attName.IsEmpty()) { + delete pelNew; + return NULL; + } + + // verify that namespaced names are unique + if (!attName.Namespace().empty()) { + if (seenNonlocalAtts.count(attName)) { + delete pelNew; + return NULL; + } + seenNonlocalAtts.insert(attName); + } + + pelNew->AddAttr(attName, std::string(*(atts + 1))); + atts += 2; + } + + return pelNew; +} + +void +XmlBuilder::StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) { + XmlElement * pelNew = BuildElement(pctx, name, atts); + if (pelNew == NULL) { + pctx->RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (!pelCurrent_) { + pelCurrent_ = pelNew; + pelRoot_.reset(pelNew); + pvParents_->push_back(NULL); + } else { + pelCurrent_->AddElement(pelNew); + pvParents_->push_back(pelCurrent_); + pelCurrent_ = pelNew; + } +} + +void +XmlBuilder::EndElement(XmlParseContext * pctx, const char * name) { + RTC_UNUSED(pctx); + RTC_UNUSED(name); + pelCurrent_ = pvParents_->back(); + pvParents_->pop_back(); +} + +void +XmlBuilder::CharacterData(XmlParseContext * pctx, + const char * text, int len) { + RTC_UNUSED(pctx); + if (pelCurrent_) { + pelCurrent_->AddParsedText(text, len); + } +} + +void +XmlBuilder::Error(XmlParseContext * pctx, XML_Error err) { + RTC_UNUSED(pctx); + RTC_UNUSED(err); + pelRoot_.reset(NULL); + pelCurrent_ = NULL; + pvParents_->clear(); +} + +XmlElement * +XmlBuilder::CreateElement() { + return pelRoot_.release(); +} + +XmlElement * +XmlBuilder::BuiltElement() { + return pelRoot_.get(); +} + +XmlBuilder::~XmlBuilder() { +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmllite/xmlbuilder.h b/webrtc/libjingle/xmllite/xmlbuilder.h new file mode 100644 index 0000000000..3b48f28247 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlbuilder.h @@ -0,0 +1,61 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef _xmlbuilder_h_ +#define _xmlbuilder_h_ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/xmlparser.h" + +#ifdef EXPAT_RELATIVE_PATH +#include "expat.h" +#else +#include "third_party/expat/v2_0_1/Source/lib/expat.h" +#endif // EXPAT_RELATIVE_PATH + +namespace buzz { + +class XmlElement; +class XmlParseContext; + + +class XmlBuilder : public XmlParseHandler { +public: + XmlBuilder(); + + static XmlElement * BuildElement(XmlParseContext * pctx, + const char * name, const char ** atts); + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts); + virtual void EndElement(XmlParseContext * pctx, const char * name); + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len); + virtual void Error(XmlParseContext * pctx, XML_Error); + virtual ~XmlBuilder(); + + void Reset(); + + // Take ownership of the built element; second call returns NULL + XmlElement * CreateElement(); + + // Peek at the built element without taking ownership + XmlElement * BuiltElement(); + +private: + XmlElement * pelCurrent_; + std::unique_ptr pelRoot_; + std::unique_ptr > pvParents_; +}; + +} + +#endif diff --git a/webrtc/libjingle/xmllite/xmlbuilder_unittest.cc b/webrtc/libjingle/xmllite/xmlbuilder_unittest.cc new file mode 100644 index 0000000000..8a5ce08d65 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlbuilder_unittest.cc @@ -0,0 +1,177 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/xmlbuilder.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlparser.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::XmlBuilder; +using buzz::XmlElement; +using buzz::XmlParser; + +TEST(XmlBuilderTest, TestTrivial) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_EQ("", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestAttributes1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_EQ("", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestAttributes2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_EQ("", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestNesting1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + ""); + EXPECT_EQ("", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestNesting2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + "" + "" + ""); + EXPECT_EQ("" + "" + "", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestQuoting1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_EQ("", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestQuoting2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_EQ("", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestQuoting3) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_EQ("", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestQuoting4) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_EQ("", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestQuoting5) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + ""); + EXPECT_EQ("", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestText1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ">"); + EXPECT_EQ(">", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestText2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<>&""); + EXPECT_EQ("<>&\"", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestText3) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "so <important>"); + EXPECT_EQ("so <important>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestText4) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<important>, yes"); + EXPECT_EQ("<important>, yes", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestText5) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + "importance &<important>&"); + EXPECT_EQ("importance &<important>&", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestNamespace1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_EQ("", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestNamespace2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_EQ("", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestNamespace3) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_TRUE(NULL == builder.BuiltElement()); +} + +TEST(XmlBuilderTest, TestNamespace4) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_TRUE(NULL == builder.BuiltElement()); +} + +TEST(XmlBuilderTest, TestAttrCollision1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, ""); + EXPECT_TRUE(NULL == builder.BuiltElement()); +} + +TEST(XmlBuilderTest, TestAttrCollision2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + ""); + EXPECT_TRUE(NULL == builder.BuiltElement()); +} + +TEST(XmlBuilderTest, TestAttrCollision3) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + "" + ""); + EXPECT_TRUE(NULL == builder.BuiltElement()); +} + diff --git a/webrtc/libjingle/xmllite/xmlconstants.cc b/webrtc/libjingle/xmllite/xmlconstants.cc new file mode 100644 index 0000000000..0b9a9ff525 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlconstants.cc @@ -0,0 +1,25 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlconstants.h" + +namespace buzz { + +const char STR_EMPTY[] = ""; +const char NS_XML[] = "http://www.w3.org/XML/1998/namespace"; +const char NS_XMLNS[] = "http://www.w3.org/2000/xmlns/"; +const char STR_XMLNS[] = "xmlns"; +const char STR_XML[] = "xml"; +const char STR_VERSION[] = "version"; +const char STR_ENCODING[] = "encoding"; + +const StaticQName QN_XMLNS = { STR_EMPTY, STR_XMLNS }; + +} // namespace buzz diff --git a/webrtc/libjingle/xmllite/xmlconstants.h b/webrtc/libjingle/xmllite/xmlconstants.h new file mode 100644 index 0000000000..0064ee9d08 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlconstants.h @@ -0,0 +1,30 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_XMLCONSTANTS_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_XMLCONSTANTS_H_ + +#include "webrtc/libjingle/xmllite/qname.h" + +namespace buzz { + +extern const char STR_EMPTY[]; +extern const char NS_XML[]; +extern const char NS_XMLNS[]; +extern const char STR_XMLNS[]; +extern const char STR_XML[]; +extern const char STR_VERSION[]; +extern const char STR_ENCODING[]; + +extern const StaticQName QN_XMLNS; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMLLITE_XMLCONSTANTS_H_ diff --git a/webrtc/libjingle/xmllite/xmlelement.cc b/webrtc/libjingle/xmllite/xmlelement.cc new file mode 100644 index 0000000000..2d90836ec1 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlelement.cc @@ -0,0 +1,496 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlelement.h" + +#include +#include +#include +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlbuilder.h" +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlparser.h" +#include "webrtc/libjingle/xmllite/xmlprinter.h" +#include "webrtc/base/common.h" + +namespace buzz { + +XmlChild::~XmlChild() { +} + +bool XmlText::IsTextImpl() const { + return true; +} + +XmlElement* XmlText::AsElementImpl() const { + return NULL; +} + +XmlText* XmlText::AsTextImpl() const { + return const_cast(this); +} + +void XmlText::SetText(const std::string& text) { + text_ = text; +} + +void XmlText::AddParsedText(const char* buf, int len) { + text_.append(buf, len); +} + +void XmlText::AddText(const std::string& text) { + text_ += text; +} + +XmlText::~XmlText() { +} + +XmlElement::XmlElement(const QName& name) : + name_(name), + first_attr_(NULL), + last_attr_(NULL), + first_child_(NULL), + last_child_(NULL), + cdata_(false) { +} + +XmlElement::XmlElement(const XmlElement& elt) : + XmlChild(), + name_(elt.name_), + first_attr_(NULL), + last_attr_(NULL), + first_child_(NULL), + last_child_(NULL), + cdata_(false) { + + // copy attributes + XmlAttr* attr; + XmlAttr ** plast_attr = &first_attr_; + XmlAttr* newAttr = NULL; + for (attr = elt.first_attr_; attr; attr = attr->NextAttr()) { + newAttr = new XmlAttr(*attr); + *plast_attr = newAttr; + plast_attr = &(newAttr->next_attr_); + } + last_attr_ = newAttr; + + // copy children + XmlChild* pChild; + XmlChild ** ppLast = &first_child_; + XmlChild* newChild = NULL; + + for (pChild = elt.first_child_; pChild; pChild = pChild->NextChild()) { + if (pChild->IsText()) { + newChild = new XmlText(*(pChild->AsText())); + } else { + newChild = new XmlElement(*(pChild->AsElement())); + } + *ppLast = newChild; + ppLast = &(newChild->next_child_); + } + last_child_ = newChild; + + cdata_ = elt.cdata_; +} + +XmlElement::XmlElement(const QName& name, bool useDefaultNs) : + name_(name), + first_attr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL), + last_attr_(first_attr_), + first_child_(NULL), + last_child_(NULL), + cdata_(false) { +} + +bool XmlElement::IsTextImpl() const { + return false; +} + +XmlElement* XmlElement::AsElementImpl() const { + return const_cast(this); +} + +XmlText* XmlElement::AsTextImpl() const { + return NULL; +} + +const std::string XmlElement::BodyText() const { + if (first_child_ && first_child_->IsText() && last_child_ == first_child_) { + return first_child_->AsText()->Text(); + } + + return std::string(); +} + +void XmlElement::SetBodyText(const std::string& text) { + if (text.empty()) { + ClearChildren(); + } else if (first_child_ == NULL) { + AddText(text); + } else if (first_child_->IsText() && last_child_ == first_child_) { + first_child_->AsText()->SetText(text); + } else { + ClearChildren(); + AddText(text); + } +} + +const QName XmlElement::FirstElementName() const { + const XmlElement* element = FirstElement(); + if (element == NULL) + return QName(); + return element->Name(); +} + +XmlAttr* XmlElement::FirstAttr() { + return first_attr_; +} + +const std::string XmlElement::Attr(const StaticQName& name) const { + XmlAttr* attr; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + return attr->value_; + } + return std::string(); +} + +const std::string XmlElement::Attr(const QName& name) const { + XmlAttr* attr; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + return attr->value_; + } + return std::string(); +} + +bool XmlElement::HasAttr(const StaticQName& name) const { + XmlAttr* attr; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + return true; + } + return false; +} + +bool XmlElement::HasAttr(const QName& name) const { + XmlAttr* attr; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + return true; + } + return false; +} + +void XmlElement::SetAttr(const QName& name, const std::string& value) { + XmlAttr* attr; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + break; + } + if (!attr) { + attr = new XmlAttr(name, value); + if (last_attr_) + last_attr_->next_attr_ = attr; + else + first_attr_ = attr; + last_attr_ = attr; + return; + } + attr->value_ = value; +} + +void XmlElement::ClearAttr(const QName& name) { + XmlAttr* attr; + XmlAttr* last_attr = NULL; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + break; + last_attr = attr; + } + if (!attr) + return; + if (!last_attr) + first_attr_ = attr->next_attr_; + else + last_attr->next_attr_ = attr->next_attr_; + if (last_attr_ == attr) + last_attr_ = last_attr; + delete attr; +} + +XmlChild* XmlElement::FirstChild() { + return first_child_; +} + +XmlElement* XmlElement::FirstElement() { + XmlChild* pChild; + for (pChild = first_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText()) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement* XmlElement::NextElement() { + XmlChild* pChild; + for (pChild = next_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText()) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement* XmlElement::FirstWithNamespace(const std::string& ns) { + XmlChild* pChild; + for (pChild = first_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextWithNamespace(const std::string& ns) { + XmlChild* pChild; + for (pChild = next_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::FirstNamed(const QName& name) { + XmlChild* pChild; + for (pChild = first_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::FirstNamed(const StaticQName& name) { + XmlChild* pChild; + for (pChild = first_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextNamed(const QName& name) { + XmlChild* pChild; + for (pChild = next_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextNamed(const StaticQName& name) { + XmlChild* pChild; + for (pChild = next_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement* XmlElement::FindOrAddNamedChild(const QName& name) { + XmlElement* child = FirstNamed(name); + if (!child) { + child = new XmlElement(name); + AddElement(child); + } + + return child; +} + +const std::string XmlElement::TextNamed(const QName& name) const { + XmlChild* pChild; + for (pChild = first_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement()->BodyText(); + } + return std::string(); +} + +void XmlElement::InsertChildAfter(XmlChild* predecessor, XmlChild* next) { + if (predecessor == NULL) { + next->next_child_ = first_child_; + first_child_ = next; + } + else { + next->next_child_ = predecessor->next_child_; + predecessor->next_child_ = next; + } +} + +void XmlElement::RemoveChildAfter(XmlChild* predecessor) { + XmlChild* next; + + if (predecessor == NULL) { + next = first_child_; + first_child_ = next->next_child_; + } + else { + next = predecessor->next_child_; + predecessor->next_child_ = next->next_child_; + } + + if (last_child_ == next) + last_child_ = predecessor; + + delete next; +} + +void XmlElement::AddAttr(const QName& name, const std::string& value) { + ASSERT(!HasAttr(name)); + + XmlAttr ** pprev = last_attr_ ? &(last_attr_->next_attr_) : &first_attr_; + last_attr_ = (*pprev = new XmlAttr(name, value)); +} + +void XmlElement::AddAttr(const QName& name, const std::string& value, + int depth) { + XmlElement* element = this; + while (depth--) { + element = element->last_child_->AsElement(); + } + element->AddAttr(name, value); +} + +void XmlElement::AddParsedText(const char* cstr, int len) { + if (len == 0) + return; + + if (last_child_ && last_child_->IsText()) { + last_child_->AsText()->AddParsedText(cstr, len); + return; + } + XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_; + last_child_ = *pprev = new XmlText(cstr, len); +} + +void XmlElement::AddCDATAText(const char* buf, int len) { + cdata_ = true; + AddParsedText(buf, len); +} + +void XmlElement::AddText(const std::string& text) { + if (text == STR_EMPTY) + return; + + if (last_child_ && last_child_->IsText()) { + last_child_->AsText()->AddText(text); + return; + } + XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_; + last_child_ = *pprev = new XmlText(text); +} + +void XmlElement::AddText(const std::string& text, int depth) { + // note: the first syntax is ambigious for msvc 6 + // XmlElement* pel(this); + XmlElement* element = this; + while (depth--) { + element = element->last_child_->AsElement(); + } + element->AddText(text); +} + +void XmlElement::AddElement(XmlElement *child) { + if (child == NULL) + return; + + XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_; + *pprev = child; + last_child_ = child; + child->next_child_ = NULL; +} + +void XmlElement::AddElement(XmlElement *child, int depth) { + XmlElement* element = this; + while (depth--) { + element = element->last_child_->AsElement(); + } + element->AddElement(child); +} + +void XmlElement::ClearNamedChildren(const QName& name) { + XmlChild* prev_child = NULL; + XmlChild* next_child; + XmlChild* child; + for (child = FirstChild(); child; child = next_child) { + next_child = child->NextChild(); + if (!child->IsText() && child->AsElement()->Name() == name) + { + RemoveChildAfter(prev_child); + continue; + } + prev_child = child; + } +} + +void XmlElement::ClearAttributes() { + XmlAttr* attr; + for (attr = first_attr_; attr; ) { + XmlAttr* to_delete = attr; + attr = attr->next_attr_; + delete to_delete; + } + first_attr_ = last_attr_ = NULL; +} + +void XmlElement::ClearChildren() { + XmlChild* pchild; + for (pchild = first_child_; pchild; ) { + XmlChild* to_delete = pchild; + pchild = pchild->next_child_; + delete to_delete; + } + first_child_ = last_child_ = NULL; +} + +std::string XmlElement::Str() const { + std::stringstream ss; + XmlPrinter::PrintXml(&ss, this); + return ss.str(); +} + +XmlElement* XmlElement::ForStr(const std::string& str) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, str); + return builder.CreateElement(); +} + +XmlElement::~XmlElement() { + XmlAttr* attr; + for (attr = first_attr_; attr; ) { + XmlAttr* to_delete = attr; + attr = attr->next_attr_; + delete to_delete; + } + + XmlChild* pchild; + for (pchild = first_child_; pchild; ) { + XmlChild* to_delete = pchild; + pchild = pchild->next_child_; + delete to_delete; + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmllite/xmlelement.h b/webrtc/libjingle/xmllite/xmlelement.h new file mode 100644 index 0000000000..37adfc0170 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlelement.h @@ -0,0 +1,233 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_XMLELEMENT_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_XMLELEMENT_H_ + +#include +#include + +#include "webrtc/libjingle/xmllite/qname.h" + +namespace buzz { + +class XmlChild; +class XmlText; +class XmlElement; +class XmlAttr; + +class XmlChild { + public: + XmlChild* NextChild() { return next_child_; } + const XmlChild* NextChild() const { return next_child_; } + + bool IsText() const { return IsTextImpl(); } + + XmlElement* AsElement() { return AsElementImpl(); } + const XmlElement* AsElement() const { return AsElementImpl(); } + + XmlText* AsText() { return AsTextImpl(); } + const XmlText* AsText() const { return AsTextImpl(); } + + + protected: + XmlChild() : + next_child_(NULL) { + } + + virtual bool IsTextImpl() const = 0; + virtual XmlElement* AsElementImpl() const = 0; + virtual XmlText* AsTextImpl() const = 0; + + + virtual ~XmlChild(); + + private: + friend class XmlElement; + + XmlChild(const XmlChild& noimpl); + + XmlChild* next_child_; +}; + +class XmlText : public XmlChild { + public: + explicit XmlText(const std::string& text) : + XmlChild(), + text_(text) { + } + explicit XmlText(const XmlText& t) : + XmlChild(), + text_(t.text_) { + } + explicit XmlText(const char* cstr, size_t len) : + XmlChild(), + text_(cstr, len) { + } + virtual ~XmlText(); + + const std::string& Text() const { return text_; } + void SetText(const std::string& text); + void AddParsedText(const char* buf, int len); + void AddText(const std::string& text); + + protected: + virtual bool IsTextImpl() const; + virtual XmlElement* AsElementImpl() const; + virtual XmlText* AsTextImpl() const; + + private: + std::string text_; +}; + +class XmlAttr { + public: + XmlAttr* NextAttr() const { return next_attr_; } + const QName& Name() const { return name_; } + const std::string& Value() const { return value_; } + + private: + friend class XmlElement; + + explicit XmlAttr(const QName& name, const std::string& value) : + next_attr_(NULL), + name_(name), + value_(value) { + } + explicit XmlAttr(const XmlAttr& att) : + next_attr_(NULL), + name_(att.name_), + value_(att.value_) { + } + + XmlAttr* next_attr_; + QName name_; + std::string value_; +}; + +class XmlElement : public XmlChild { + public: + explicit XmlElement(const QName& name); + explicit XmlElement(const QName& name, bool useDefaultNs); + explicit XmlElement(const XmlElement& elt); + + virtual ~XmlElement(); + + const QName& Name() const { return name_; } + void SetName(const QName& name) { name_ = name; } + + const std::string BodyText() const; + void SetBodyText(const std::string& text); + + const QName FirstElementName() const; + + XmlAttr* FirstAttr(); + const XmlAttr* FirstAttr() const + { return const_cast(this)->FirstAttr(); } + + // Attr will return an empty string if the attribute isn't there: + // use HasAttr to test presence of an attribute. + const std::string Attr(const StaticQName& name) const; + const std::string Attr(const QName& name) const; + bool HasAttr(const StaticQName& name) const; + bool HasAttr(const QName& name) const; + void SetAttr(const QName& name, const std::string& value); + void ClearAttr(const QName& name); + + XmlChild* FirstChild(); + const XmlChild* FirstChild() const { + return const_cast(this)->FirstChild(); + } + + XmlElement* FirstElement(); + const XmlElement* FirstElement() const { + return const_cast(this)->FirstElement(); + } + + XmlElement* NextElement(); + const XmlElement* NextElement() const { + return const_cast(this)->NextElement(); + } + + XmlElement* FirstWithNamespace(const std::string& ns); + const XmlElement* FirstWithNamespace(const std::string& ns) const { + return const_cast(this)->FirstWithNamespace(ns); + } + + XmlElement* NextWithNamespace(const std::string& ns); + const XmlElement* NextWithNamespace(const std::string& ns) const { + return const_cast(this)->NextWithNamespace(ns); + } + + XmlElement* FirstNamed(const StaticQName& name); + const XmlElement* FirstNamed(const StaticQName& name) const { + return const_cast(this)->FirstNamed(name); + } + + XmlElement* FirstNamed(const QName& name); + const XmlElement* FirstNamed(const QName& name) const { + return const_cast(this)->FirstNamed(name); + } + + XmlElement* NextNamed(const StaticQName& name); + const XmlElement* NextNamed(const StaticQName& name) const { + return const_cast(this)->NextNamed(name); + } + + XmlElement* NextNamed(const QName& name); + const XmlElement* NextNamed(const QName& name) const { + return const_cast(this)->NextNamed(name); + } + + // Finds the first element named 'name'. If that element can't be found then + // adds one and returns it. + XmlElement* FindOrAddNamedChild(const QName& name); + + const std::string TextNamed(const QName& name) const; + + void InsertChildAfter(XmlChild* predecessor, XmlChild* new_child); + void RemoveChildAfter(XmlChild* predecessor); + + void AddParsedText(const char* buf, int len); + // Note: CDATA is not supported by XMPP, therefore using this function will + // generate non-XMPP compatible XML. + void AddCDATAText(const char* buf, int len); + void AddText(const std::string& text); + void AddText(const std::string& text, int depth); + void AddElement(XmlElement* child); + void AddElement(XmlElement* child, int depth); + void AddAttr(const QName& name, const std::string& value); + void AddAttr(const QName& name, const std::string& value, int depth); + void ClearNamedChildren(const QName& name); + void ClearAttributes(); + void ClearChildren(); + + static XmlElement* ForStr(const std::string& str); + std::string Str() const; + + bool IsCDATA() const { return cdata_; } + + protected: + virtual bool IsTextImpl() const; + virtual XmlElement* AsElementImpl() const; + virtual XmlText* AsTextImpl() const; + + private: + QName name_; + XmlAttr* first_attr_; + XmlAttr* last_attr_; + XmlChild* first_child_; + XmlChild* last_child_; + bool cdata_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMLLITE_XMLELEMENT_H_ diff --git a/webrtc/libjingle/xmllite/xmlelement_unittest.cc b/webrtc/libjingle/xmllite/xmlelement_unittest.cc new file mode 100644 index 0000000000..df8faedbf0 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlelement_unittest.cc @@ -0,0 +1,258 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" + +using buzz::QName; +using buzz::XmlAttr; +using buzz::XmlChild; +using buzz::XmlElement; + +std::ostream& operator<<(std::ostream& os, const QName& name) { + os << name.Namespace() << ":" << name.LocalPart(); + return os; +} + +TEST(XmlElementTest, TestConstructors) { + XmlElement elt(QName("google:test", "first")); + EXPECT_EQ("", elt.Str()); + + XmlElement elt2(QName("google:test", "first"), true); + EXPECT_EQ("", elt2.Str()); +} + +TEST(XmlElementTest, TestAdd) { + XmlElement elt(QName("google:test", "root"), true); + elt.AddElement(new XmlElement(QName("google:test", "first"))); + elt.AddElement(new XmlElement(QName("google:test", "nested")), 1); + elt.AddText("nested-value", 2); + elt.AddText("between-", 1); + elt.AddText("value", 1); + elt.AddElement(new XmlElement(QName("google:test", "nested2")), 1); + elt.AddElement(new XmlElement(QName("google:test", "second"))); + elt.AddText("init-value", 1); + elt.AddElement(new XmlElement(QName("google:test", "nested3")), 1); + elt.AddText("trailing-value", 1); + + // make sure it looks ok overall + EXPECT_EQ("" + "nested-valuebetween-value" + "init-valuetrailing-value", + elt.Str()); + + // make sure text was concatenated + XmlChild * pchild = + elt.FirstChild()->AsElement()->FirstChild()->NextChild(); + EXPECT_TRUE(pchild->IsText()); + EXPECT_EQ("between-value", pchild->AsText()->Text()); +} + +TEST(XmlElementTest, TestAttrs) { + XmlElement elt(QName("", "root")); + elt.SetAttr(QName("", "a"), "avalue"); + EXPECT_EQ("", elt.Str()); + + elt.SetAttr(QName("", "b"), "bvalue"); + EXPECT_EQ("", elt.Str()); + + elt.SetAttr(QName("", "a"), "avalue2"); + EXPECT_EQ("", elt.Str()); + + elt.SetAttr(QName("", "b"), "bvalue2"); + EXPECT_EQ("", elt.Str()); + + elt.SetAttr(QName("", "c"), "cvalue"); + EXPECT_EQ("", elt.Str()); + + XmlAttr * patt = elt.FirstAttr(); + EXPECT_EQ(QName("", "a"), patt->Name()); + EXPECT_EQ("avalue2", patt->Value()); + + patt = patt->NextAttr(); + EXPECT_EQ(QName("", "b"), patt->Name()); + EXPECT_EQ("bvalue2", patt->Value()); + + patt = patt->NextAttr(); + EXPECT_EQ(QName("", "c"), patt->Name()); + EXPECT_EQ("cvalue", patt->Value()); + + patt = patt->NextAttr(); + EXPECT_TRUE(NULL == patt); + + EXPECT_TRUE(elt.HasAttr(QName("", "a"))); + EXPECT_TRUE(elt.HasAttr(QName("", "b"))); + EXPECT_TRUE(elt.HasAttr(QName("", "c"))); + EXPECT_FALSE(elt.HasAttr(QName("", "d"))); + + elt.SetAttr(QName("", "d"), "dvalue"); + EXPECT_EQ("", + elt.Str()); + EXPECT_TRUE(elt.HasAttr(QName("", "d"))); + + elt.ClearAttr(QName("", "z")); // not found, no effect + EXPECT_EQ("", + elt.Str()); + + elt.ClearAttr(QName("", "b")); + EXPECT_EQ("", elt.Str()); + + elt.ClearAttr(QName("", "a")); + EXPECT_EQ("", elt.Str()); + + elt.ClearAttr(QName("", "d")); + EXPECT_EQ("", elt.Str()); + + elt.ClearAttr(QName("", "c")); + EXPECT_EQ("", elt.Str()); +} + +TEST(XmlElementTest, TestBodyText) { + XmlElement elt(QName("", "root")); + EXPECT_EQ("", elt.BodyText()); + + elt.AddText("body value text"); + + EXPECT_EQ("body value text", elt.BodyText()); + + elt.ClearChildren(); + elt.AddText("more value "); + elt.AddText("text"); + + EXPECT_EQ("more value text", elt.BodyText()); + + elt.ClearChildren(); + elt.AddText("decoy"); + elt.AddElement(new XmlElement(QName("", "dummy"))); + EXPECT_EQ("", elt.BodyText()); + + elt.SetBodyText("replacement"); + EXPECT_EQ("replacement", elt.BodyText()); + + elt.SetBodyText(""); + EXPECT_TRUE(NULL == elt.FirstChild()); + + elt.SetBodyText("goodbye"); + EXPECT_EQ("goodbye", elt.FirstChild()->AsText()->Text()); + EXPECT_EQ("goodbye", elt.BodyText()); +} + +TEST(XmlElementTest, TestCopyConstructor) { + XmlElement * element = XmlElement::ForStr( + "This is a " + "little little test"); + + XmlElement * pelCopy = new XmlElement(*element); + EXPECT_EQ("This is a " + "little little test", pelCopy->Str()); + delete pelCopy; + + pelCopy = new XmlElement(*(element->FirstChild()->NextChild()->AsElement())); + EXPECT_EQ("" + "little little", pelCopy->Str()); + + XmlAttr * patt = pelCopy->FirstAttr(); + EXPECT_EQ(QName("", "a"), patt->Name()); + EXPECT_EQ("avalue", patt->Value()); + + patt = patt->NextAttr(); + EXPECT_EQ(QName("", "b"), patt->Name()); + EXPECT_EQ("bvalue", patt->Value()); + + patt = patt->NextAttr(); + EXPECT_TRUE(NULL == patt); + delete pelCopy; + delete element; +} + +TEST(XmlElementTest, TestNameSearch) { + XmlElement * element = XmlElement::ForStr( + "" + "George" + "X." + "some text" + "Harrison" + "John" + "Y." + "Lennon" + ""); + EXPECT_TRUE(NULL == + element->FirstNamed(QName("", "firstname"))); + EXPECT_EQ(element->FirstChild(), + element->FirstNamed(QName("test-foo", "firstname"))); + EXPECT_EQ(element->FirstChild()->NextChild(), + element->FirstNamed(QName("test-foo", "middlename"))); + EXPECT_EQ(element->FirstElement()->NextElement(), + element->FirstNamed(QName("test-foo", "middlename"))); + EXPECT_EQ("Harrison", + element->TextNamed(QName("test-foo", "lastname"))); + EXPECT_EQ(element->FirstElement()->NextElement()->NextElement(), + element->FirstNamed(QName("test-foo", "lastname"))); + EXPECT_EQ("John", element->FirstNamed(QName("test-foo", "firstname"))-> + NextNamed(QName("test-foo", "firstname"))->BodyText()); + EXPECT_EQ("Y.", element->FirstNamed(QName("test-foo", "middlename"))-> + NextNamed(QName("test-foo", "middlename"))->BodyText()); + EXPECT_EQ("Lennon", element->FirstNamed(QName("test-foo", "lastname"))-> + NextNamed(QName("test-foo", "lastname"))->BodyText()); + EXPECT_TRUE(NULL == element->FirstNamed(QName("test-foo", "firstname"))-> + NextNamed(QName("test-foo", "firstname"))-> + NextNamed(QName("test-foo", "firstname"))); + + delete element; +} + +class XmlElementCreatorThread : public rtc::Thread { + public: + XmlElementCreatorThread(int count, buzz::QName qname) : + count_(count), qname_(qname) {} + + virtual ~XmlElementCreatorThread() { + Stop(); + } + + virtual void Run() { + std::vector elems; + for (int i = 0; i < count_; i++) { + elems.push_back(new XmlElement(qname_)); + } + for (int i = 0; i < count_; i++) { + delete elems[i]; + } + } + + private: + int count_; + buzz::QName qname_; +}; + +// If XmlElement creation and destruction isn't thread safe, +// this test should crash. +TEST(XmlElementTest, TestMultithread) { + int thread_count = 2; // Was 100, but that's too slow. + int elem_count = 100; // Was 100000, but that's too slow. + buzz::QName qname("foo", "bar"); + + std::vector threads; + for (int i = 0; i < thread_count; i++) { + threads.push_back( + new XmlElementCreatorThread(elem_count, qname)); + threads[i]->Start(); + } + + for (int i = 0; i < thread_count; i++) { + threads[i]->Stop(); + delete threads[i]; + } +} diff --git a/webrtc/libjingle/xmllite/xmlnsstack.cc b/webrtc/libjingle/xmllite/xmlnsstack.cc new file mode 100644 index 0000000000..bac2fc695e --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlnsstack.cc @@ -0,0 +1,178 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlnsstack.h" + +#include +#include +#include + +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" + +namespace buzz { + +XmlnsStack::XmlnsStack() : + pxmlnsStack_(new std::vector), + pxmlnsDepthStack_(new std::vector) { +} + +XmlnsStack::~XmlnsStack() {} + +void XmlnsStack::PushFrame() { + pxmlnsDepthStack_->push_back(pxmlnsStack_->size()); +} + +void XmlnsStack::PopFrame() { + size_t prev_size = pxmlnsDepthStack_->back(); + pxmlnsDepthStack_->pop_back(); + if (prev_size < pxmlnsStack_->size()) { + pxmlnsStack_->erase(pxmlnsStack_->begin() + prev_size, + pxmlnsStack_->end()); + } +} + +std::pair XmlnsStack::NsForPrefix( + const std::string& prefix) { + if (prefix.length() >= 3 && + (prefix[0] == 'x' || prefix[0] == 'X') && + (prefix[1] == 'm' || prefix[1] == 'M') && + (prefix[2] == 'l' || prefix[2] == 'L')) { + if (prefix == "xml") + return std::make_pair(NS_XML, true); + if (prefix == "xmlns") + return std::make_pair(NS_XMLNS, true); + // Other names with xml prefix are illegal. + return std::make_pair(STR_EMPTY, false); + } + + std::vector::iterator pos; + for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) { + pos -= 2; + if (*pos == prefix) + return std::make_pair(*(pos + 1), true); + } + + if (prefix == STR_EMPTY) + return std::make_pair(STR_EMPTY, true); // default namespace + + return std::make_pair(STR_EMPTY, false); // none found +} + +bool XmlnsStack::PrefixMatchesNs(const std::string& prefix, + const std::string& ns) { + const std::pair match = NsForPrefix(prefix); + return match.second && (match.first == ns); +} + +std::pair XmlnsStack::PrefixForNs(const std::string& ns, + bool isattr) { + if (ns == NS_XML) + return std::make_pair(std::string("xml"), true); + if (ns == NS_XMLNS) + return std::make_pair(std::string("xmlns"), true); + if (isattr ? ns == STR_EMPTY : PrefixMatchesNs(STR_EMPTY, ns)) + return std::make_pair(STR_EMPTY, true); + + std::vector::iterator pos; + for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) { + pos -= 2; + if (*(pos + 1) == ns && + (!isattr || !pos->empty()) && PrefixMatchesNs(*pos, ns)) + return std::make_pair(*pos, true); + } + + return std::make_pair(STR_EMPTY, false); // none found +} + +std::string XmlnsStack::FormatQName(const QName& name, bool isAttr) { + std::string prefix(PrefixForNs(name.Namespace(), isAttr).first); + if (prefix == STR_EMPTY) + return name.LocalPart(); + else + return prefix + ':' + name.LocalPart(); +} + +void XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) { + pxmlnsStack_->push_back(prefix); + pxmlnsStack_->push_back(ns); +} + +void XmlnsStack::RemoveXmlns() { + pxmlnsStack_->pop_back(); + pxmlnsStack_->pop_back(); +} + +static bool IsAsciiLetter(char ch) { + return ((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z')); +} + +static std::string AsciiLower(const std::string & s) { + std::string result(s); + size_t i; + for (i = 0; i < result.length(); i++) { + if (result[i] >= 'A' && result[i] <= 'Z') + result[i] += 'a' - 'A'; + } + return result; +} + +static std::string SuggestPrefix(const std::string & ns) { + size_t len = ns.length(); + size_t i = ns.find_last_of('.'); + if (i != std::string::npos && len - i <= 4 + 1) + len = i; // chop off ".html" or ".xsd" or ".?{0,4}" + size_t last = len; + while (last > 0) { + last -= 1; + if (IsAsciiLetter(ns[last])) { + size_t first = last; + last += 1; + while (first > 0) { + if (!IsAsciiLetter(ns[first - 1])) + break; + first -= 1; + } + if (last - first > 4) + last = first + 3; + std::string candidate(AsciiLower(ns.substr(first, last - first))); + if (candidate.find("xml") != 0) + return candidate; + break; + } + } + return "ns"; +} + +std::pair XmlnsStack::AddNewPrefix(const std::string& ns, + bool isAttr) { + if (PrefixForNs(ns, isAttr).second) + return std::make_pair(STR_EMPTY, false); + + std::string base(SuggestPrefix(ns)); + std::string result(base); + int i = 2; + while (NsForPrefix(result).second) { + std::stringstream ss; + ss << base; + ss << (i++); + ss >> result; + } + AddXmlns(result, ns); + return std::make_pair(result, true); +} + +void XmlnsStack::Reset() { + pxmlnsStack_->clear(); + pxmlnsDepthStack_->clear(); +} + +} diff --git a/webrtc/libjingle/xmllite/xmlnsstack.h b/webrtc/libjingle/xmllite/xmlnsstack.h new file mode 100644 index 0000000000..64e2f6ee66 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlnsstack.h @@ -0,0 +1,45 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_XMLNSSTACK_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_XMLNSSTACK_H_ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/qname.h" + +namespace buzz { + +class XmlnsStack { +public: + XmlnsStack(); + ~XmlnsStack(); + + void AddXmlns(const std::string& prefix, const std::string& ns); + void RemoveXmlns(); + void PushFrame(); + void PopFrame(); + void Reset(); + + std::pair NsForPrefix(const std::string& prefix); + bool PrefixMatchesNs(const std::string & prefix, const std::string & ns); + std::pair PrefixForNs(const std::string& ns, bool isAttr); + std::pair AddNewPrefix(const std::string& ns, bool isAttr); + std::string FormatQName(const QName & name, bool isAttr); + +private: + + std::unique_ptr > pxmlnsStack_; + std::unique_ptr > pxmlnsDepthStack_; +}; +} + +#endif // WEBRTC_LIBJINGLE_XMLLITE_XMLNSSTACK_H_ diff --git a/webrtc/libjingle/xmllite/xmlnsstack_unittest.cc b/webrtc/libjingle/xmllite/xmlnsstack_unittest.cc new file mode 100644 index 0000000000..a57a4b0764 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlnsstack_unittest.cc @@ -0,0 +1,241 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlnsstack.h" + +#include +#include +#include + +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::NS_XML; +using buzz::NS_XMLNS; +using buzz::QName; +using buzz::XmlnsStack; + +TEST(XmlnsStackTest, TestBuiltin) { + XmlnsStack stack; + + EXPECT_EQ(std::string(NS_XML), stack.NsForPrefix("xml").first); + EXPECT_EQ(std::string(NS_XMLNS), stack.NsForPrefix("xmlns").first); + EXPECT_EQ("", stack.NsForPrefix("").first); + + EXPECT_EQ("xml", stack.PrefixForNs(NS_XML, false).first); + EXPECT_EQ("xmlns", stack.PrefixForNs(NS_XMLNS, false).first); + EXPECT_EQ("", stack.PrefixForNs("", false).first); + EXPECT_EQ("", stack.PrefixForNs("", true).first); +} + +TEST(XmlnsStackTest, TestNsForPrefix) { + XmlnsStack stack; + stack.AddXmlns("pre1", "ns1"); + stack.AddXmlns("pre2", "ns2"); + stack.AddXmlns("pre1", "ns3"); + stack.AddXmlns("", "ns4"); + + EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first); + EXPECT_TRUE(stack.NsForPrefix("pre1").second); + EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first); + EXPECT_EQ("ns4", stack.NsForPrefix("").first); + EXPECT_EQ("", stack.NsForPrefix("pre3").first); + EXPECT_FALSE(stack.NsForPrefix("pre3").second); +} + +TEST(XmlnsStackTest, TestPrefixForNs) { + XmlnsStack stack; + stack.AddXmlns("pre1", "ns1"); + stack.AddXmlns("pre2", "ns2"); + stack.AddXmlns("pre1", "ns3"); + stack.AddXmlns("pre3", "ns2"); + stack.AddXmlns("pre4", "ns4"); + stack.AddXmlns("", "ns4"); + + EXPECT_EQ("", stack.PrefixForNs("ns1", false).first); + EXPECT_FALSE(stack.PrefixForNs("ns1", false).second); + EXPECT_EQ("", stack.PrefixForNs("ns1", true).first); + EXPECT_FALSE(stack.PrefixForNs("ns1", true).second); + EXPECT_EQ("pre3", stack.PrefixForNs("ns2", false).first); + EXPECT_TRUE(stack.PrefixForNs("ns2", false).second); + EXPECT_EQ("pre3", stack.PrefixForNs("ns2", true).first); + EXPECT_TRUE(stack.PrefixForNs("ns2", true).second); + EXPECT_EQ("pre1", stack.PrefixForNs("ns3", false).first); + EXPECT_EQ("pre1", stack.PrefixForNs("ns3", true).first); + EXPECT_EQ("", stack.PrefixForNs("ns4", false).first); + EXPECT_TRUE(stack.PrefixForNs("ns4", false).second); + EXPECT_EQ("pre4", stack.PrefixForNs("ns4", true).first); + EXPECT_EQ("", stack.PrefixForNs("ns5", false).first); + EXPECT_FALSE(stack.PrefixForNs("ns5", false).second); + EXPECT_EQ("", stack.PrefixForNs("ns5", true).first); + EXPECT_EQ("", stack.PrefixForNs("", false).first); + EXPECT_EQ("", stack.PrefixForNs("", true).first); + + stack.AddXmlns("", "ns6"); + EXPECT_EQ("", stack.PrefixForNs("ns6", false).first); + EXPECT_TRUE(stack.PrefixForNs("ns6", false).second); + EXPECT_EQ("", stack.PrefixForNs("ns6", true).first); + EXPECT_FALSE(stack.PrefixForNs("ns6", true).second); +} + +TEST(XmlnsStackTest, TestFrames) { + XmlnsStack stack; + stack.PushFrame(); + stack.AddXmlns("pre1", "ns1"); + stack.AddXmlns("pre2", "ns2"); + + stack.PushFrame(); + stack.AddXmlns("pre1", "ns3"); + stack.AddXmlns("pre3", "ns2"); + stack.AddXmlns("pre4", "ns4"); + + stack.PushFrame(); + stack.PushFrame(); + stack.AddXmlns("", "ns4"); + + // basic test + EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first); + EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first); + EXPECT_EQ("", stack.NsForPrefix("pre5").first); + EXPECT_FALSE(stack.NsForPrefix("pre5").second); + EXPECT_EQ("ns4", stack.NsForPrefix("").first); + EXPECT_TRUE(stack.NsForPrefix("").second); + + // pop the default xmlns definition + stack.PopFrame(); + EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first); + EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first); + EXPECT_EQ("", stack.NsForPrefix("pre5").first); + EXPECT_FALSE(stack.NsForPrefix("pre5").second); + EXPECT_EQ("", stack.NsForPrefix("").first); + EXPECT_TRUE(stack.NsForPrefix("").second); + + // pop empty frame (nop) + stack.PopFrame(); + EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first); + EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first); + EXPECT_EQ("", stack.NsForPrefix("pre5").first); + EXPECT_FALSE(stack.NsForPrefix("pre5").second); + EXPECT_EQ("", stack.NsForPrefix("").first); + EXPECT_TRUE(stack.NsForPrefix("").second); + + // pop frame with three defs + stack.PopFrame(); + EXPECT_EQ("ns1", stack.NsForPrefix("pre1").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first); + EXPECT_EQ("", stack.NsForPrefix("pre3").first); + EXPECT_FALSE(stack.NsForPrefix("pre3").second); + EXPECT_EQ("", stack.NsForPrefix("pre4").first); + EXPECT_FALSE(stack.NsForPrefix("pre4").second); + EXPECT_EQ("", stack.NsForPrefix("pre5").first); + EXPECT_FALSE(stack.NsForPrefix("pre5").second); + EXPECT_EQ("", stack.NsForPrefix("").first); + EXPECT_TRUE(stack.NsForPrefix("").second); + + // pop frame with last two defs + stack.PopFrame(); + EXPECT_FALSE(stack.NsForPrefix("pre1").second); + EXPECT_FALSE(stack.NsForPrefix("pre2").second); + EXPECT_FALSE(stack.NsForPrefix("pre3").second); + EXPECT_FALSE(stack.NsForPrefix("pre4").second); + EXPECT_FALSE(stack.NsForPrefix("pre5").second); + EXPECT_TRUE(stack.NsForPrefix("").second); + EXPECT_EQ("", stack.NsForPrefix("pre1").first); + EXPECT_EQ("", stack.NsForPrefix("pre2").first); + EXPECT_EQ("", stack.NsForPrefix("pre3").first); + EXPECT_EQ("", stack.NsForPrefix("pre4").first); + EXPECT_EQ("", stack.NsForPrefix("pre5").first); + EXPECT_EQ("", stack.NsForPrefix("").first); +} + +TEST(XmlnsStackTest, TestAddNewPrefix) { + XmlnsStack stack; + + // builtin namespaces cannot be added + EXPECT_FALSE(stack.AddNewPrefix("", true).second); + EXPECT_FALSE(stack.AddNewPrefix("", false).second); + EXPECT_FALSE(stack.AddNewPrefix(NS_XML, true).second); + EXPECT_FALSE(stack.AddNewPrefix(NS_XML, false).second); + EXPECT_FALSE(stack.AddNewPrefix(NS_XMLNS, true).second); + EXPECT_FALSE(stack.AddNewPrefix(NS_XMLNS, false).second); + + // namespaces already added cannot be added again. + EXPECT_EQ("foo", stack.AddNewPrefix("http://a.b.com/foo.htm", true).first); + EXPECT_EQ("bare", stack.AddNewPrefix("http://a.b.com/bare", false).first); + EXPECT_EQ("z", stack.AddNewPrefix("z", false).first); + EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/foo.htm", true).second); + EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/bare", true).second); + EXPECT_FALSE(stack.AddNewPrefix("z", true).second); + EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/foo.htm", false).second); + EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/bare", false).second); + EXPECT_FALSE(stack.AddNewPrefix("z", false).second); + + // default namespace usable by non-attributes only + stack.AddXmlns("", "http://my/default"); + EXPECT_FALSE(stack.AddNewPrefix("http://my/default", false).second); + EXPECT_EQ("def", stack.AddNewPrefix("http://my/default", true).first); + + // namespace cannot start with 'xml' + EXPECT_EQ("ns", stack.AddNewPrefix("http://a.b.com/xmltest", true).first); + EXPECT_EQ("ns2", stack.AddNewPrefix("xmlagain", false).first); + + // verify added namespaces are still defined + EXPECT_EQ("http://a.b.com/foo.htm", stack.NsForPrefix("foo").first); + EXPECT_TRUE(stack.NsForPrefix("foo").second); + EXPECT_EQ("http://a.b.com/bare", stack.NsForPrefix("bare").first); + EXPECT_TRUE(stack.NsForPrefix("bare").second); + EXPECT_EQ("z", stack.NsForPrefix("z").first); + EXPECT_TRUE(stack.NsForPrefix("z").second); + EXPECT_EQ("http://my/default", stack.NsForPrefix("").first); + EXPECT_TRUE(stack.NsForPrefix("").second); + EXPECT_EQ("http://my/default", stack.NsForPrefix("def").first); + EXPECT_TRUE(stack.NsForPrefix("def").second); + EXPECT_EQ("http://a.b.com/xmltest", stack.NsForPrefix("ns").first); + EXPECT_TRUE(stack.NsForPrefix("ns").second); + EXPECT_EQ("xmlagain", stack.NsForPrefix("ns2").first); + EXPECT_TRUE(stack.NsForPrefix("ns2").second); +} + +TEST(XmlnsStackTest, TestFormatQName) { + XmlnsStack stack; + stack.AddXmlns("pre1", "ns1"); + stack.AddXmlns("pre2", "ns2"); + stack.AddXmlns("pre1", "ns3"); + stack.AddXmlns("", "ns4"); + + EXPECT_EQ("zip", + stack.FormatQName(QName("ns1", "zip"), false)); // no match + EXPECT_EQ("pre2:abracadabra", + stack.FormatQName(QName("ns2", "abracadabra"), false)); + EXPECT_EQ("pre1:a", + stack.FormatQName(QName("ns3", "a"), false)); + EXPECT_EQ("simple", + stack.FormatQName(QName("ns4", "simple"), false)); + EXPECT_EQ("root", + stack.FormatQName(QName("", "root"), false)); // no match + + EXPECT_EQ("zip", + stack.FormatQName(QName("ns1", "zip"), true)); // no match + EXPECT_EQ("pre2:abracadabra", + stack.FormatQName(QName("ns2", "abracadabra"), true)); + EXPECT_EQ("pre1:a", + stack.FormatQName(QName("ns3", "a"), true)); + EXPECT_EQ("simple", + stack.FormatQName(QName("ns4", "simple"), true)); // no match + EXPECT_EQ("root", + stack.FormatQName(QName("", "root"), true)); +} diff --git a/webrtc/libjingle/xmllite/xmlparser.cc b/webrtc/libjingle/xmllite/xmlparser.cc new file mode 100644 index 0000000000..bdb8be7d9d --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlparser.cc @@ -0,0 +1,261 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlparser.h" + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlnsstack.h" +#include "webrtc/libjingle/xmllite/xmlnsstack.h" +#include "webrtc/base/common.h" + +namespace buzz { + + +static void +StartElementCallback(void * userData, const char *name, const char **atts) { + (static_cast(userData))->ExpatStartElement(name, atts); +} + +static void +EndElementCallback(void * userData, const char *name) { + (static_cast(userData))->ExpatEndElement(name); +} + +static void +CharacterDataCallback(void * userData, const char *text, int len) { + (static_cast(userData))->ExpatCharacterData(text, len); +} + +static void +XmlDeclCallback(void * userData, const char * ver, const char * enc, int st) { + (static_cast(userData))->ExpatXmlDecl(ver, enc, st); +} + +XmlParser::XmlParser(XmlParseHandler *pxph) : + pxph_(pxph), sentError_(false) { + expat_ = XML_ParserCreate(NULL); + XML_SetUserData(expat_, this); + XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback); + XML_SetCharacterDataHandler(expat_, CharacterDataCallback); + XML_SetXmlDeclHandler(expat_, XmlDeclCallback); +} + +void +XmlParser::Reset() { + if (!XML_ParserReset(expat_, NULL)) { + XML_ParserFree(expat_); + expat_ = XML_ParserCreate(NULL); + } + XML_SetUserData(expat_, this); + XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback); + XML_SetCharacterDataHandler(expat_, CharacterDataCallback); + XML_SetXmlDeclHandler(expat_, XmlDeclCallback); + context_.Reset(); + sentError_ = false; +} + +static bool +XmlParser_StartsWithXmlns(const char *name) { + return name[0] == 'x' && + name[1] == 'm' && + name[2] == 'l' && + name[3] == 'n' && + name[4] == 's'; +} + +void +XmlParser::ExpatStartElement(const char *name, const char **atts) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + const char **att; + context_.StartElement(); + for (att = atts; *att; att += 2) { + if (XmlParser_StartsWithXmlns(*att)) { + if ((*att)[5] == '\0') { + context_.StartNamespace("", *(att + 1)); + } + else if ((*att)[5] == ':') { + if (**(att + 1) == '\0') { + // In XML 1.0 empty namespace illegal with prefix (not in 1.1) + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + context_.StartNamespace((*att) + 6, *(att + 1)); + } + } + } + context_.SetPosition(XML_GetCurrentLineNumber(expat_), + XML_GetCurrentColumnNumber(expat_), + XML_GetCurrentByteIndex(expat_)); + pxph_->StartElement(&context_, name, atts); +} + +void +XmlParser::ExpatEndElement(const char *name) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + context_.EndElement(); + context_.SetPosition(XML_GetCurrentLineNumber(expat_), + XML_GetCurrentColumnNumber(expat_), + XML_GetCurrentByteIndex(expat_)); + pxph_->EndElement(&context_, name); +} + +void +XmlParser::ExpatCharacterData(const char *text, int len) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + context_.SetPosition(XML_GetCurrentLineNumber(expat_), + XML_GetCurrentColumnNumber(expat_), + XML_GetCurrentByteIndex(expat_)); + pxph_->CharacterData(&context_, text, len); +} + +void +XmlParser::ExpatXmlDecl(const char * ver, const char * enc, int standalone) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + + if (ver && std::string("1.0") != ver) { + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (standalone == 0) { + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (enc && !((enc[0] == 'U' || enc[0] == 'u') && + (enc[1] == 'T' || enc[1] == 't') && + (enc[2] == 'F' || enc[2] == 'f') && + enc[3] == '-' && enc[4] =='8')) { + context_.RaiseError(XML_ERROR_INCORRECT_ENCODING); + return; + } + +} + +bool +XmlParser::Parse(const char *data, size_t len, bool isFinal) { + if (sentError_) + return false; + + if (XML_Parse(expat_, data, static_cast(len), isFinal) != + XML_STATUS_OK) { + context_.SetPosition(XML_GetCurrentLineNumber(expat_), + XML_GetCurrentColumnNumber(expat_), + XML_GetCurrentByteIndex(expat_)); + context_.RaiseError(XML_GetErrorCode(expat_)); + } + + if (context_.RaisedError() != XML_ERROR_NONE) { + sentError_ = true; + pxph_->Error(&context_, context_.RaisedError()); + return false; + } + + return true; +} + +XmlParser::~XmlParser() { + XML_ParserFree(expat_); +} + +void +XmlParser::ParseXml(XmlParseHandler *pxph, std::string text) { + XmlParser parser(pxph); + parser.Parse(text.c_str(), text.length(), true); +} + +XmlParser::ParseContext::ParseContext() : + xmlnsstack_(), + raised_(XML_ERROR_NONE), + line_number_(0), + column_number_(0), + byte_index_(0) { +} + +void +XmlParser::ParseContext::StartNamespace(const char *prefix, const char *ns) { + xmlnsstack_.AddXmlns(*prefix ? prefix : STR_EMPTY, ns); +} + +void +XmlParser::ParseContext::StartElement() { + xmlnsstack_.PushFrame(); +} + +void +XmlParser::ParseContext::EndElement() { + xmlnsstack_.PopFrame(); +} + +QName +XmlParser::ParseContext::ResolveQName(const char* qname, bool isAttr) { + const char *c; + for (c = qname; *c; ++c) { + if (*c == ':') { + const std::pair result = + xmlnsstack_.NsForPrefix(std::string(qname, c - qname)); + if (!result.second) + return QName(); + return QName(result.first, c + 1); + } + } + if (isAttr) + return QName(STR_EMPTY, qname); + + std::pair result = xmlnsstack_.NsForPrefix(STR_EMPTY); + if (!result.second) + return QName(); + + return QName(result.first, qname); +} + +void +XmlParser::ParseContext::Reset() { + xmlnsstack_.Reset(); + raised_ = XML_ERROR_NONE; +} + +void +XmlParser::ParseContext::SetPosition(int line, int column, + long byte_index) { + line_number_ = line; + column_number_ = column; + byte_index_ = byte_index; +} + +void +XmlParser::ParseContext::GetPosition(unsigned long * line, + unsigned long * column, + unsigned long * byte_index) { + if (line != NULL) { + *line = static_cast(line_number_); + } + + if (column != NULL) { + *column = static_cast(column_number_); + } + + if (byte_index != NULL) { + *byte_index = static_cast(byte_index_); + } +} + +XmlParser::ParseContext::~ParseContext() { +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmllite/xmlparser.h b/webrtc/libjingle/xmllite/xmlparser.h new file mode 100644 index 0000000000..131c585e4d --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlparser.h @@ -0,0 +1,103 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_XMLPARSER_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_XMLPARSER_H_ + +#include + +#include "webrtc/libjingle/xmllite/xmlnsstack.h" +#ifdef EXPAT_RELATIVE_PATH +#include "expat.h" +#else +#include "third_party/expat/v2_0_1/Source/lib/expat.h" +#endif // EXPAT_RELATIVE_PATH + +struct XML_ParserStruct; +typedef struct XML_ParserStruct* XML_Parser; + +namespace buzz { + +class XmlParseHandler; +class XmlParseContext; +class XmlParser; + +class XmlParseContext { +public: + virtual ~XmlParseContext() {} + virtual QName ResolveQName(const char * qname, bool isAttr) = 0; + virtual void RaiseError(XML_Error err) = 0; + virtual void GetPosition(unsigned long * line, unsigned long * column, + unsigned long * byte_index) = 0; +}; + +class XmlParseHandler { +public: + virtual ~XmlParseHandler() {} + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) = 0; + virtual void EndElement(XmlParseContext * pctx, + const char * name) = 0; + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len) = 0; + virtual void Error(XmlParseContext * pctx, + XML_Error errorCode) = 0; +}; + +class XmlParser { +public: + static void ParseXml(XmlParseHandler * pxph, std::string text); + + explicit XmlParser(XmlParseHandler * pxph); + bool Parse(const char * data, size_t len, bool isFinal); + void Reset(); + virtual ~XmlParser(); + + // expat callbacks + void ExpatStartElement(const char * name, const char ** atts); + void ExpatEndElement(const char * name); + void ExpatCharacterData(const char * text, int len); + void ExpatXmlDecl(const char * ver, const char * enc, int standalone); + +private: + + class ParseContext : public XmlParseContext { + public: + ParseContext(); + virtual ~ParseContext(); + virtual QName ResolveQName(const char * qname, bool isAttr); + virtual void RaiseError(XML_Error err) { if (!raised_) raised_ = err; } + virtual void GetPosition(unsigned long * line, unsigned long * column, + unsigned long * byte_index); + XML_Error RaisedError() { return raised_; } + void Reset(); + + void StartElement(); + void EndElement(); + void StartNamespace(const char * prefix, const char * ns); + void SetPosition(int line, int column, long byte_index); + + private: + XmlnsStack xmlnsstack_; + XML_Error raised_; + XML_Size line_number_; + XML_Size column_number_; + XML_Index byte_index_; + }; + + ParseContext context_; + XML_Parser expat_; + XmlParseHandler * pxph_; + bool sentError_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMLLITE_XMLPARSER_H_ diff --git a/webrtc/libjingle/xmllite/xmlparser_unittest.cc b/webrtc/libjingle/xmllite/xmlparser_unittest.cc new file mode 100644 index 0000000000..a73d81e6f3 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlparser_unittest.cc @@ -0,0 +1,285 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlparser.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::QName; +using buzz::XmlParser; +using buzz::XmlParseContext; +using buzz::XmlParseHandler; + +class XmlParserTestHandler : public XmlParseHandler { + public: + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) { + ss_ << "START (" << pctx->ResolveQName(name, false).Merged(); + while (*atts) { + ss_ << ", " << pctx->ResolveQName(*atts, true).Merged() + << "='" << *(atts+1) << "'"; + atts += 2; + } + ss_ << ") "; + } + virtual void EndElement(XmlParseContext * pctx, const char * name) { + RTC_UNUSED(pctx); + RTC_UNUSED(name); + ss_ << "END "; + } + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len) { + RTC_UNUSED(pctx); + ss_ << "TEXT (" << std::string(text, len) << ") "; + } + virtual void Error(XmlParseContext * pctx, XML_Error code) { + RTC_UNUSED(pctx); + ss_ << "ERROR (" << static_cast(code) << ") "; + } + virtual ~XmlParserTestHandler() { + } + + std::string Str() { + return ss_.str(); + } + + std::string StrClear() { + std::string result = ss_.str(); + ss_.str(""); + return result; + } + + private: + std::stringstream ss_; +}; + + +TEST(XmlParserTest, TestTrivial) { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, ""); + EXPECT_EQ("START (testing) END ", handler.Str()); +} + +TEST(XmlParserTest, TestAttributes) { + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, ""); + EXPECT_EQ("START (testing, a='b') END ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, ""); + EXPECT_EQ("START (testing, e='', long='some text') END ", handler.Str()); + } +} + +TEST(XmlParserTest, TestNesting) { + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + ""); + EXPECT_EQ("START (top) START (first) END START (second) START (third) " + "END END END ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "" + "" + ""); + EXPECT_EQ("START (top) START (fifth) START (deeper) START (and) START " + "(deeper) END END START (sibling) START (leaf) END END END " + "END START (first) END START (second) START (third) END END END ", + handler.Str()); + } +} + +TEST(XmlParserTest, TestXmlDecl) { + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, ""); + EXPECT_EQ("START (testing) END ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + ""); + EXPECT_EQ("START (testing) END ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + "" + ""); + EXPECT_EQ("START (testing) END ", handler.Str()); + } +} + +TEST(XmlParserTest, TestNamespace) { + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, ""); + EXPECT_EQ("START (my-namespace:top, xmlns='my-namespace', a='b') END ", + handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, ""); + EXPECT_EQ("START (my-namespace:top, " + "http://www.w3.org/2000/xmlns/:foo='my-namespace', " + "a='b', my-namespace:c='d') END ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "" + ""); + EXPECT_EQ("START (top) START (my-namespace:nested, xmlns='my-namespace') " + "START (my-namespace:leaf) END END START (sibling) END END ", + handler.Str()); + } +} + +TEST(XmlParserTest, TestIncremental) { + XmlParserTestHandler handler; + XmlParser parser(&handler); + std::string fragment; + + fragment = " garbage "); + EXPECT_EQ("START (top) END ERROR (9) ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<-hm->"); + EXPECT_EQ("ERROR (4) ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "&foobar;"); + EXPECT_EQ("START (hello) ERROR (11) ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + ""); + EXPECT_EQ("ERROR (3) ", handler.Str()); + } + { + // XmlParser requires utf-8 + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + ""); + EXPECT_EQ("ERROR (19) ", handler.Str()); + } + { + // XmlParser requires version 1.0 + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + ""); + EXPECT_EQ("ERROR (2) ", handler.Str()); + } + { + // XmlParser requires standalone documents + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + ""); + EXPECT_EQ("ERROR (2) ", handler.Str()); + } + { + // XmlParser doesn't like empty namespace URIs + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + ""); + EXPECT_EQ("ERROR (2) ", handler.Str()); + } +} diff --git a/webrtc/libjingle/xmllite/xmlprinter.cc b/webrtc/libjingle/xmllite/xmlprinter.cc new file mode 100644 index 0000000000..27d7cc0bfe --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlprinter.cc @@ -0,0 +1,174 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlprinter.h" + +#include +#include +#include + +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlnsstack.h" + +namespace buzz { + +class XmlPrinterImpl { +public: + XmlPrinterImpl(std::ostream* pout, XmlnsStack* ns_stack); + void PrintElement(const XmlElement* element); + void PrintQuotedValue(const std::string& text); + void PrintBodyText(const std::string& text); + void PrintCDATAText(const std::string& text); + +private: + std::ostream *pout_; + XmlnsStack* ns_stack_; +}; + +void XmlPrinter::PrintXml(std::ostream* pout, const XmlElement* element) { + XmlnsStack ns_stack; + PrintXml(pout, element, &ns_stack); +} + +void XmlPrinter::PrintXml(std::ostream* pout, const XmlElement* element, + XmlnsStack* ns_stack) { + XmlPrinterImpl printer(pout, ns_stack); + printer.PrintElement(element); +} + +XmlPrinterImpl::XmlPrinterImpl(std::ostream* pout, XmlnsStack* ns_stack) + : pout_(pout), + ns_stack_(ns_stack) { +} + +void XmlPrinterImpl::PrintElement(const XmlElement* element) { + ns_stack_->PushFrame(); + + // first go through attrs of pel to add xmlns definitions + const XmlAttr* attr; + for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) { + if (attr->Name() == QN_XMLNS) { + ns_stack_->AddXmlns(STR_EMPTY, attr->Value()); + } else if (attr->Name().Namespace() == NS_XMLNS) { + ns_stack_->AddXmlns(attr->Name().LocalPart(), + attr->Value()); + } + } + + // then go through qnames to make sure needed xmlns definitons are added + std::vector new_ns; + std::pair prefix; + prefix = ns_stack_->AddNewPrefix(element->Name().Namespace(), false); + if (prefix.second) { + new_ns.push_back(prefix.first); + new_ns.push_back(element->Name().Namespace()); + } + + for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) { + prefix = ns_stack_->AddNewPrefix(attr->Name().Namespace(), true); + if (prefix.second) { + new_ns.push_back(prefix.first); + new_ns.push_back(attr->Name().Namespace()); + } + } + + // print the element name + *pout_ << '<' << ns_stack_->FormatQName(element->Name(), false); + + // and the attributes + for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) { + *pout_ << ' ' << ns_stack_->FormatQName(attr->Name(), true) << "=\""; + PrintQuotedValue(attr->Value()); + *pout_ << '"'; + } + + // and the extra xmlns declarations + std::vector::iterator i(new_ns.begin()); + while (i < new_ns.end()) { + if (*i == STR_EMPTY) { + *pout_ << " xmlns=\"" << *(i + 1) << '"'; + } else { + *pout_ << " xmlns:" << *i << "=\"" << *(i + 1) << '"'; + } + i += 2; + } + + // now the children + const XmlChild* child = element->FirstChild(); + + if (child == NULL) + *pout_ << "/>"; + else { + *pout_ << '>'; + while (child) { + if (child->IsText()) { + if (element->IsCDATA()) { + PrintCDATAText(child->AsText()->Text()); + } else { + PrintBodyText(child->AsText()->Text()); + } + } else { + PrintElement(child->AsElement()); + } + child = child->NextChild(); + } + *pout_ << "FormatQName(element->Name(), false) << '>'; + } + + ns_stack_->PopFrame(); +} + +void XmlPrinterImpl::PrintQuotedValue(const std::string& text) { + size_t safe = 0; + for (;;) { + size_t unsafe = text.find_first_of("<>&\"", safe); + if (unsafe == std::string::npos) + unsafe = text.length(); + *pout_ << text.substr(safe, unsafe - safe); + if (unsafe == text.length()) + return; + switch (text[unsafe]) { + case '<': *pout_ << "<"; break; + case '>': *pout_ << ">"; break; + case '&': *pout_ << "&"; break; + case '"': *pout_ << """; break; + } + safe = unsafe + 1; + if (safe == text.length()) + return; + } +} + +void XmlPrinterImpl::PrintBodyText(const std::string& text) { + size_t safe = 0; + for (;;) { + size_t unsafe = text.find_first_of("<>&", safe); + if (unsafe == std::string::npos) + unsafe = text.length(); + *pout_ << text.substr(safe, unsafe - safe); + if (unsafe == text.length()) + return; + switch (text[unsafe]) { + case '<': *pout_ << "<"; break; + case '>': *pout_ << ">"; break; + case '&': *pout_ << "&"; break; + } + safe = unsafe + 1; + if (safe == text.length()) + return; + } +} + +void XmlPrinterImpl::PrintCDATAText(const std::string& text) { + *pout_ << ""; +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmllite/xmlprinter.h b/webrtc/libjingle/xmllite/xmlprinter.h new file mode 100644 index 0000000000..40cf195f70 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlprinter.h @@ -0,0 +1,32 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_XMLPRINTER_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_XMLPRINTER_H_ + +#include +#include + +namespace buzz { + +class XmlElement; +class XmlnsStack; + +class XmlPrinter { + public: + static void PrintXml(std::ostream* pout, const XmlElement* pelt); + + static void PrintXml(std::ostream* pout, const XmlElement* pelt, + XmlnsStack* ns_stack); +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMLLITE_XMLPRINTER_H_ diff --git a/webrtc/libjingle/xmllite/xmlprinter_unittest.cc b/webrtc/libjingle/xmllite/xmlprinter_unittest.cc new file mode 100644 index 0000000000..b4850b5a47 --- /dev/null +++ b/webrtc/libjingle/xmllite/xmlprinter_unittest.cc @@ -0,0 +1,45 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlprinter.h" + +#include +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlnsstack.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::QName; +using buzz::XmlElement; +using buzz::XmlnsStack; +using buzz::XmlPrinter; + +TEST(XmlPrinterTest, TestBasicPrinting) { + XmlElement elt(QName("google:test", "first")); + std::stringstream ss; + XmlPrinter::PrintXml(&ss, &elt); + EXPECT_EQ("", ss.str()); +} + +TEST(XmlPrinterTest, TestNamespacedPrinting) { + XmlElement elt(QName("google:test", "first")); + elt.AddElement(new XmlElement(QName("nested:test", "second"))); + std::stringstream ss; + + XmlnsStack ns_stack; + ns_stack.AddXmlns("gg", "google:test"); + ns_stack.AddXmlns("", "nested:test"); + + XmlPrinter::PrintXml(&ss, &elt, &ns_stack); + EXPECT_EQ("", ss.str()); +} diff --git a/webrtc/libjingle/xmpp/BUILD.gn b/webrtc/libjingle/xmpp/BUILD.gn new file mode 100644 index 0000000000..17a52fbaad --- /dev/null +++ b/webrtc/libjingle/xmpp/BUILD.gn @@ -0,0 +1,154 @@ +# Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +import("../../build/webrtc.gni") + +group("xmpp") { + public_deps = [ + ":rtc_xmpp", + ] +} + +config("xmpp_warnings_config") { + # GN orders flags on a target before flags from configs. The default config + # adds these flags so to cancel them out they need to come from a config and + # cannot be on the target directly. + if (is_android) { + cflags = [ "-Wno-error" ] + } +} + +config("xmpp_inherited_config") { + defines = [ + "FEATURE_ENABLE_SSL", + "FEATURE_ENABLE_VOICEMAIL", + ] +} + +rtc_static_library("rtc_xmpp") { + cflags = [] + sources = [ + "asyncsocket.h", + "constants.cc", + "constants.h", + "jid.cc", + "jid.h", + "plainsaslhandler.h", + "prexmppauth.h", + "saslcookiemechanism.h", + "saslhandler.h", + "saslmechanism.cc", + "saslmechanism.h", + "saslplainmechanism.h", + "xmppclient.cc", + "xmppclient.h", + "xmppclientsettings.h", + "xmppengine.h", + "xmppengineimpl.cc", + "xmppengineimpl.h", + "xmppengineimpl_iq.cc", + "xmpplogintask.cc", + "xmpplogintask.h", + "xmppstanzaparser.cc", + "xmppstanzaparser.h", + "xmpptask.cc", + "xmpptask.h", + ] + + defines = [ "FEATURE_ENABLE_SSL" ] + + deps = [ + "../../base:rtc_base", + "../xmllite", + ] + + if (rtc_build_expat) { + deps += [ "//third_party/expat" ] + public_deps = [ + "//third_party/expat", + ] + } + + configs += [ ":xmpp_warnings_config" ] + + public_configs = [ ":xmpp_inherited_config" ] + + if (build_with_chromium) { + if (is_nacl) { + deps += [ "//native_client_sdk/src/libraries/nacl_io" ] + } + } else { + sources += [ + "chatroommodule.h", + "chatroommoduleimpl.cc", + "discoitemsquerytask.cc", + "discoitemsquerytask.h", + "hangoutpubsubclient.cc", + "hangoutpubsubclient.h", + "iqtask.cc", + "iqtask.h", + "module.h", + "moduleimpl.cc", + "moduleimpl.h", + "mucroomconfigtask.cc", + "mucroomconfigtask.h", + "mucroomdiscoverytask.cc", + "mucroomdiscoverytask.h", + "mucroomlookuptask.cc", + "mucroomlookuptask.h", + "mucroomuniquehangoutidtask.cc", + "mucroomuniquehangoutidtask.h", + "pingtask.cc", + "pingtask.h", + "presenceouttask.cc", + "presenceouttask.h", + "presencereceivetask.cc", + "presencereceivetask.h", + "presencestatus.cc", + "presencestatus.h", + "pubsub_task.cc", + "pubsub_task.h", + "pubsubclient.cc", + "pubsubclient.h", + "pubsubstateclient.cc", + "pubsubstateclient.h", + "pubsubtasks.cc", + "pubsubtasks.h", + "receivetask.cc", + "receivetask.h", + "rostermodule.h", + "rostermoduleimpl.cc", + "rostermoduleimpl.h", + "xmppauth.cc", + "xmppauth.h", + "xmpppump.cc", + "xmpppump.h", + "xmppsocket.cc", + "xmppsocket.h", + "xmppthread.cc", + "xmppthread.h", + ] + + defines += [ + "FEATURE_ENABLE_VOICEMAIL", + "FEATURE_ENABLE_PSTN", + ] + + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + } + + if (is_posix && is_debug) { + # The Chromium build/common.gypi defines this for all posix + # _except_ for ios & mac. We want it there as well, e.g. + # because ASSERT and friends trigger off of it. + defines += [ "_DEBUG" ] + } +} diff --git a/webrtc/libjingle/xmpp/OWNERS b/webrtc/libjingle/xmpp/OWNERS new file mode 100644 index 0000000000..6d6e52a6ae --- /dev/null +++ b/webrtc/libjingle/xmpp/OWNERS @@ -0,0 +1,6 @@ +# See ../OWNERS for more owners. + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gn=* +per-file *.gni=* diff --git a/webrtc/libjingle/xmpp/asyncsocket.h b/webrtc/libjingle/xmpp/asyncsocket.h new file mode 100644 index 0000000000..6d77ce0842 --- /dev/null +++ b/webrtc/libjingle/xmpp/asyncsocket.h @@ -0,0 +1,72 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_ASYNCSOCKET_H_ +#define WEBRTC_LIBJINGLE_XMPP_ASYNCSOCKET_H_ + +#include + +#include "webrtc/base/sigslot.h" + +namespace rtc { + class SocketAddress; +} + +namespace buzz { + +class AsyncSocket { +public: + enum State { + STATE_CLOSED = 0, //!< Socket is not open. + STATE_CLOSING, //!< Socket is closing but can have buffered data + STATE_CONNECTING, //!< In the process of + STATE_OPEN, //!< Socket is connected +#if defined(FEATURE_ENABLE_SSL) + STATE_TLS_CONNECTING, //!< Establishing TLS connection + STATE_TLS_OPEN, //!< TLS connected +#endif + }; + + enum Error { + ERROR_NONE = 0, //!< No error + ERROR_WINSOCK, //!< Winsock error + ERROR_DNS, //!< Couldn't resolve host name + ERROR_WRONGSTATE, //!< Call made while socket is in the wrong state +#if defined(FEATURE_ENABLE_SSL) + ERROR_SSL, //!< Something went wrong with OpenSSL +#endif + }; + + virtual ~AsyncSocket() {} + virtual State state() = 0; + virtual Error error() = 0; + virtual int GetError() = 0; // winsock error code + + virtual bool Connect(const rtc::SocketAddress& addr) = 0; + virtual bool Read(char * data, size_t len, size_t* len_read) = 0; + virtual bool Write(const char * data, size_t len) = 0; + virtual bool Close() = 0; +#if defined(FEATURE_ENABLE_SSL) + // We allow matching any passed domain. This allows us to avoid + // handling the valuable certificates for logins into proxies. If + // both names are passed as empty, we do not require a match. + virtual bool StartTls(const std::string & domainname) = 0; +#endif + + sigslot::signal0<> SignalConnected; + sigslot::signal0<> SignalSSLConnected; + sigslot::signal0<> SignalClosed; + sigslot::signal0<> SignalRead; + sigslot::signal0<> SignalError; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_ASYNCSOCKET_H_ diff --git a/webrtc/libjingle/xmpp/chatroommodule.h b/webrtc/libjingle/xmpp/chatroommodule.h new file mode 100644 index 0000000000..a68f82b34a --- /dev/null +++ b/webrtc/libjingle/xmpp/chatroommodule.h @@ -0,0 +1,253 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_CHATROOMMODULE_H_ +#define WEBRTC_LIBJINGLE_XMPP_CHATROOMMODULE_H_ + +#include "webrtc/libjingle/xmpp/module.h" +#include "webrtc/libjingle/xmpp/rostermodule.h" + +namespace buzz { + +// forward declarations +class XmppChatroomModule; +class XmppChatroomHandler; +class XmppChatroomMember; +class XmppChatroomMemberEnumerator; + +enum XmppChatroomState { + XMPP_CHATROOM_STATE_NOT_IN_ROOM = 0, + XMPP_CHATROOM_STATE_REQUESTED_ENTER = 1, + XMPP_CHATROOM_STATE_IN_ROOM = 2, + XMPP_CHATROOM_STATE_REQUESTED_EXIT = 3, +}; + +//! Module that encapsulates a chatroom. +class XmppChatroomModule : public XmppModule { +public: + + //! Creates a new XmppChatroomModule + static XmppChatroomModule* Create(); + virtual ~XmppChatroomModule() {} + + //! Sets the chatroom handler (callbacks) for the chatroom + virtual XmppReturnStatus set_chatroom_handler(XmppChatroomHandler* handler) = 0; + + //! Gets the chatroom handler for the module + virtual XmppChatroomHandler* chatroom_handler() = 0; + + //! Sets the jid of the chatroom. + //! Has to be set before entering the chatroom and can't be changed + //! while in the chatroom + virtual XmppReturnStatus set_chatroom_jid(const Jid& chatroom_jid) = 0; + + //! The jid for the chatroom + virtual const Jid& chatroom_jid() const = 0; + + //! Sets the nickname of the member + //! Has to be set before entering the chatroom and can't be changed + //! while in the chatroom + virtual XmppReturnStatus set_nickname(const std::string& nickname) = 0; + + //! The nickname of the member in the chatroom + virtual const std::string& nickname() const = 0; + + //! Returns the jid of the member (this is the chatroom_jid plus the + //! nickname as the resource name) + virtual const Jid member_jid() const = 0; + + //! Requests that the user enter a chatroom + //! The EnterChatroom callback will be called when the request is complete. + //! Password should be empty for a room that doesn't require a password + //! If the room doesn't exist, the server will create an "Instant Room" if the + //! server policy supports this action. + //! There will be different methods for creating/configuring a "Reserved Room" + //! Async callback for this method is ChatroomEnteredStatus + virtual XmppReturnStatus RequestEnterChatroom(const std::string& password, + const std::string& client_version, + const std::string& locale) = 0; + + //! Requests that the user exit a chatroom + //! Async callback for this method is ChatroomExitedStatus + virtual XmppReturnStatus RequestExitChatroom() = 0; + + //! Requests a status change + //! status is the standard XMPP status code + //! extended_status is the extended status when status is XMPP_PRESENCE_XA + virtual XmppReturnStatus RequestConnectionStatusChange( + XmppPresenceConnectionStatus connection_status) = 0; + + //! Returns the number of members in the room + virtual size_t GetChatroomMemberCount() = 0; + + //! Gets an enumerator for the members in the chatroom + //! The caller must delete the enumerator when the caller is finished with it. + //! The caller must also ensure that the lifetime of the enumerator is + //! scoped by the XmppChatRoomModule that created it. + virtual XmppReturnStatus CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator) = 0; + + //! Gets the subject of the chatroom + virtual const std::string subject() = 0; + + //! Returns the current state of the user with respect to the chatroom + virtual XmppChatroomState state() = 0; + + virtual XmppReturnStatus SendMessage(const XmlElement& message) = 0; +}; + +//! Class for enumerating participatns +class XmppChatroomMemberEnumerator { +public: + virtual ~XmppChatroomMemberEnumerator() { } + //! Returns the member at the current position + //! Returns null if the enumerator is before the beginning + //! or after the end of the collection + virtual XmppChatroomMember* current() = 0; + + //! Returns whether the enumerator is valid + //! This returns true if the collection has changed + //! since the enumerator was created + virtual bool IsValid() = 0; + + //! Returns whether the enumerator is before the beginning + //! This is the initial state of the enumerator + virtual bool IsBeforeBeginning() = 0; + + //! Returns whether the enumerator is after the end + virtual bool IsAfterEnd() = 0; + + //! Advances the enumerator to the next position + //! Returns false is the enumerator is advanced + //! off the end of the collection + virtual bool Next() = 0; + + //! Advances the enumerator to the previous position + //! Returns false is the enumerator is advanced + //! off the end of the collection + virtual bool Prev() = 0; +}; + + +//! Represents a single member in a chatroom +class XmppChatroomMember { +public: + virtual ~XmppChatroomMember() { } + + //! The jid for the member in the chatroom + virtual const Jid member_jid() const = 0; + + //! The full jid for the member + //! This is only available in non-anonymous rooms. + //! If the room is anonymous, this returns JID_EMPTY + virtual const Jid full_jid() const = 0; + + //! Returns the backing presence for this member + virtual const XmppPresence* presence() const = 0; + + //! The nickname for this member + virtual const std::string name() const = 0; +}; + +//! Status codes for ChatroomEnteredStatus callback +enum XmppChatroomEnteredStatus +{ + //! User successfully entered the room + XMPP_CHATROOM_ENTERED_SUCCESS = 0, + //! The nickname confliced with somebody already in the room + XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT = 1, + //! A password is required to enter the room + XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED = 2, + //! The specified password was incorrect + XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_INCORRECT = 3, + //! The user is not a member of a member-only room + XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER = 4, + //! The user cannot enter because the user has been banned + XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED = 5, + //! The room has the maximum number of users already + XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS = 6, + //! The room has been locked by an administrator + XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED = 7, + //! Someone in the room has blocked you + XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKED = 8, + //! You have blocked someone in the room + XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKING = 9, + //! Client is old. User must upgrade to a more recent version for + // hangouts to work. + XMPP_CHATROOM_ENTERED_FAILURE_OUTDATED_CLIENT = 10, + //! Some other reason + XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED = 2000, +}; + +//! Status codes for ChatroomExitedStatus callback +enum XmppChatroomExitedStatus +{ + //! The user requested to exit and did so + XMPP_CHATROOM_EXITED_REQUESTED = 0, + //! The user was banned from the room + XMPP_CHATROOM_EXITED_BANNED = 1, + //! The user has been kicked out of the room + XMPP_CHATROOM_EXITED_KICKED = 2, + //! The user has been removed from the room because the + //! user is no longer a member of a member-only room + //! or the room has changed to membership-only + XMPP_CHATROOM_EXITED_NOT_A_MEMBER = 3, + //! The system is shutting down + XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN = 4, + //! For some other reason + XMPP_CHATROOM_EXITED_UNSPECIFIED = 5, +}; + +//! The XmppChatroomHandler is the interface for callbacks from the +//! the chatroom +class XmppChatroomHandler { +public: + virtual ~XmppChatroomHandler() {} + + //! Indicates the response to RequestEnterChatroom method + //! XMPP_CHATROOM_SUCCESS represents success. + //! Other status codes are for errors + virtual void ChatroomEnteredStatus(XmppChatroomModule* room, + const XmppPresence* presence, + XmppChatroomEnteredStatus status) = 0; + + + //! Indicates that the user has exited the chatroom, either due to + //! a call to RequestExitChatroom or for some other reason. + //! status indicates the reason the user exited + virtual void ChatroomExitedStatus(XmppChatroomModule* room, + XmppChatroomExitedStatus status) = 0; + + //! Indicates a member entered the room. + //! It can be called before ChatroomEnteredStatus. + virtual void MemberEntered(XmppChatroomModule* room, + const XmppChatroomMember* entered_member) = 0; + + //! Indicates that a member exited the room. + virtual void MemberExited(XmppChatroomModule* room, + const XmppChatroomMember* exited_member) = 0; + + //! Indicates that the data for the member has changed + //! (such as the nickname or presence) + virtual void MemberChanged(XmppChatroomModule* room, + const XmppChatroomMember* changed_member) = 0; + + //! Indicates a new message has been received + //! message is the message - + // $TODO - message should be changed + //! to a strongly-typed message class that contains info + //! such as the sender, message bodies, etc., + virtual void MessageReceived(XmppChatroomModule* room, + const XmlElement& message) = 0; +}; + + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_CHATROOMMODULE_H_ diff --git a/webrtc/libjingle/xmpp/chatroommoduleimpl.cc b/webrtc/libjingle/xmpp/chatroommoduleimpl.cc new file mode 100644 index 0000000000..4feb5bc9b8 --- /dev/null +++ b/webrtc/libjingle/xmpp/chatroommoduleimpl.cc @@ -0,0 +1,737 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "webrtc/libjingle/xmpp/chatroommodule.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/moduleimpl.h" +#include "webrtc/base/arraysize.h" +#include "webrtc/base/common.h" + +namespace buzz { + +// forward declarations +class XmppChatroomImpl; +class XmppChatroomMemberImpl; + +//! Module that encapsulates multiple chatrooms. +//! Each chatroom is represented by an XmppChatroomImpl instance +class XmppChatroomModuleImpl : public XmppChatroomModule, + public XmppModuleImpl, public XmppIqHandler { +public: + IMPLEMENT_XMPPMODULE + + // Creates a chatroom with specified Jid + XmppChatroomModuleImpl(); + ~XmppChatroomModuleImpl(); + + // XmppChatroomModule + virtual XmppReturnStatus set_chatroom_handler(XmppChatroomHandler* handler); + virtual XmppChatroomHandler* chatroom_handler(); + virtual XmppReturnStatus set_chatroom_jid(const Jid& chatroom_jid); + virtual const Jid& chatroom_jid() const; + virtual XmppReturnStatus set_nickname(const std::string& nickname); + virtual const std::string& nickname() const; + virtual const Jid member_jid() const; + virtual XmppReturnStatus RequestEnterChatroom(const std::string& password, + const std::string& client_version, + const std::string& locale); + virtual XmppReturnStatus RequestExitChatroom(); + virtual XmppReturnStatus RequestConnectionStatusChange( + XmppPresenceConnectionStatus connection_status); + virtual size_t GetChatroomMemberCount(); + virtual XmppReturnStatus CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator); + virtual const std::string subject(); + virtual XmppChatroomState state() { return chatroom_state_; } + virtual XmppReturnStatus SendMessage(const XmlElement& message); + + // XmppModule + virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) {RTC_UNUSED2(cookie, pelStanza);} + virtual bool HandleStanza(const XmlElement *); + +private: + friend class XmppChatroomMemberEnumeratorImpl; + + XmppReturnStatus ServerChangeMyPresence(const XmlElement& presence); + XmppReturnStatus ClientChangeMyPresence(XmppChatroomState new_state); + XmppReturnStatus ChangePresence(XmppChatroomState new_state, const XmlElement* presence, bool isServer); + XmppReturnStatus ServerChangedOtherPresence(const XmlElement& presence_element); + XmppChatroomEnteredStatus GetEnterFailureFromXml(const XmlElement* presence); + XmppChatroomExitedStatus GetExitFailureFromXml(const XmlElement* presence); + + bool CheckEnterChatroomStateOk(); + + void FireEnteredStatus(const XmlElement* presence, + XmppChatroomEnteredStatus status); + void FireExitStatus(XmppChatroomExitedStatus status); + void FireMessageReceived(const XmlElement& message); + void FireMemberEntered(const XmppChatroomMember* entered_member); + void FireMemberChanged(const XmppChatroomMember* changed_member); + void FireMemberExited(const XmppChatroomMember* exited_member); + + + typedef std::map JidMemberMap; + + XmppChatroomHandler* chatroom_handler_; + Jid chatroom_jid_; + std::string nickname_; + XmppChatroomState chatroom_state_; + JidMemberMap chatroom_jid_members_; + int chatroom_jid_members_version_; +}; + + +class XmppChatroomMemberImpl : public XmppChatroomMember { +public: + ~XmppChatroomMemberImpl() {} + XmppReturnStatus SetPresence(const XmppPresence* presence); + + // XmppChatroomMember + const Jid member_jid() const; + const Jid full_jid() const; + const std::string name() const; + const XmppPresence* presence() const; + +private: + std::unique_ptr presence_; +}; + +class XmppChatroomMemberEnumeratorImpl : + public XmppChatroomMemberEnumerator { +public: + XmppChatroomMemberEnumeratorImpl(XmppChatroomModuleImpl::JidMemberMap* chatroom_jid_members, + int* map_version); + + // XmppChatroomMemberEnumerator + virtual XmppChatroomMember* current(); + virtual bool Next(); + virtual bool Prev(); + virtual bool IsValid(); + virtual bool IsBeforeBeginning(); + virtual bool IsAfterEnd(); + +private: + XmppChatroomModuleImpl::JidMemberMap* map_; + int map_version_created_; + int* map_version_; + XmppChatroomModuleImpl::JidMemberMap::iterator iterator_; + bool before_beginning_; +}; + + +// XmppChatroomModuleImpl ------------------------------------------------ +XmppChatroomModule * +XmppChatroomModule::Create() { + return new XmppChatroomModuleImpl(); +} + +XmppChatroomModuleImpl::XmppChatroomModuleImpl() : + chatroom_handler_(NULL), + chatroom_jid_(STR_EMPTY), + chatroom_state_(XMPP_CHATROOM_STATE_NOT_IN_ROOM), + chatroom_jid_members_version_(0) { +} + +XmppChatroomModuleImpl::~XmppChatroomModuleImpl() { + JidMemberMap::iterator iterator = chatroom_jid_members_.begin(); + while (iterator != chatroom_jid_members_.end()) { + delete iterator->second; + iterator++; + } +} + + +bool +XmppChatroomModuleImpl::HandleStanza(const XmlElement* stanza) { + ASSERT(engine() != NULL); + + // we handle stanzas that are for one of our chatrooms + Jid from_jid = Jid(stanza->Attr(QN_FROM)); + // see if it's one of our chatrooms + if (chatroom_jid_ != from_jid.BareJid()) { + return false; // not one of our chatrooms + } else { + // handle presence stanza + if (stanza->Name() == QN_PRESENCE) { + if (from_jid == member_jid()) { + ServerChangeMyPresence(*stanza); + } else { + ServerChangedOtherPresence(*stanza); + } + } else if (stanza->Name() == QN_MESSAGE) { + FireMessageReceived(*stanza); + } + return true; + } +} + + +XmppReturnStatus +XmppChatroomModuleImpl::set_chatroom_handler(XmppChatroomHandler* handler) { + // Calling with NULL removes the handler. + chatroom_handler_ = handler; + return XMPP_RETURN_OK; +} + + +XmppChatroomHandler* +XmppChatroomModuleImpl::chatroom_handler() { + return chatroom_handler_; +} + +XmppReturnStatus +XmppChatroomModuleImpl::set_chatroom_jid(const Jid& chatroom_jid) { + if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) { + return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code? + } + if (chatroom_jid != chatroom_jid.BareJid()) { + // chatroom_jid must be a bare jid + return XMPP_RETURN_BADARGUMENT; + } + + chatroom_jid_ = chatroom_jid; + return XMPP_RETURN_OK; +} + +const Jid& +XmppChatroomModuleImpl::chatroom_jid() const { + return chatroom_jid_; +} + + XmppReturnStatus + XmppChatroomModuleImpl::set_nickname(const std::string& nickname) { + if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) { + return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code? + } + nickname_ = nickname; + return XMPP_RETURN_OK; + } + + const std::string& + XmppChatroomModuleImpl::nickname() const { + return nickname_; + } + +const Jid +XmppChatroomModuleImpl::member_jid() const { + return Jid(chatroom_jid_.node(), chatroom_jid_.domain(), nickname_); +} + + +bool +XmppChatroomModuleImpl::CheckEnterChatroomStateOk() { + if (chatroom_jid_.IsValid() == false) { + ASSERT(0); + return false; + } + if (nickname_ == STR_EMPTY) { + ASSERT(0); + return false; + } + return true; +} + +std::string GetAttrValueFor(XmppPresenceConnectionStatus connection_status) { + switch (connection_status) { + default: + case XMPP_CONNECTION_STATUS_UNKNOWN: + return ""; + case XMPP_CONNECTION_STATUS_CONNECTING: + return STR_PSTN_CONFERENCE_STATUS_CONNECTING; + case XMPP_CONNECTION_STATUS_CONNECTED: + return STR_PSTN_CONFERENCE_STATUS_CONNECTED; + } +} + +XmppReturnStatus +XmppChatroomModuleImpl::RequestEnterChatroom( + const std::string& password, + const std::string& client_version, + const std::string& locale) { + RTC_UNUSED(password); + if (!engine()) + return XMPP_RETURN_BADSTATE; + + if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) + return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code? + + if (CheckEnterChatroomStateOk() == false) { + return XMPP_RETURN_BADSTATE; + } + + // entering a chatroom is a presence request to the server + XmlElement element(QN_PRESENCE); + element.AddAttr(QN_TO, member_jid().Str()); + + XmlElement* muc_x = new XmlElement(QN_MUC_X); + element.AddElement(muc_x); + + if (!client_version.empty()) { + XmlElement* client_version_element = new XmlElement(QN_CLIENT_VERSION, + false); + client_version_element->SetBodyText(client_version); + muc_x->AddElement(client_version_element); + } + + if (!locale.empty()) { + XmlElement* locale_element = new XmlElement(QN_LOCALE, false); + + locale_element->SetBodyText(locale); + muc_x->AddElement(locale_element); + } + + XmppReturnStatus status = engine()->SendStanza(&element); + if (status == XMPP_RETURN_OK) { + return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_ENTER); + } + return status; +} + +XmppReturnStatus +XmppChatroomModuleImpl::RequestExitChatroom() { + if (!engine()) + return XMPP_RETURN_BADSTATE; + + // exiting a chatroom is a presence request to the server + XmlElement element(QN_PRESENCE); + element.AddAttr(QN_TO, member_jid().Str()); + element.AddAttr(QN_TYPE, "unavailable"); + XmppReturnStatus status = engine()->SendStanza(&element); + if (status == XMPP_RETURN_OK && + chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) { + return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_EXIT); + } + return status; +} + +XmppReturnStatus +XmppChatroomModuleImpl::RequestConnectionStatusChange( + XmppPresenceConnectionStatus connection_status) { + if (!engine()) + return XMPP_RETURN_BADSTATE; + + if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) { + // $TODO - this isn't a bad state, it's a bad call, diff error code? + return XMPP_RETURN_BADSTATE; + } + + if (CheckEnterChatroomStateOk() == false) { + return XMPP_RETURN_BADSTATE; + } + + // entering a chatroom is a presence request to the server + XmlElement element(QN_PRESENCE); + element.AddAttr(QN_TO, member_jid().Str()); + element.AddElement(new XmlElement(QN_MUC_X)); + if (connection_status != XMPP_CONNECTION_STATUS_UNKNOWN) { + XmlElement* con_status_element = + new XmlElement(QN_GOOGLE_PSTN_CONFERENCE_STATUS); + con_status_element->AddAttr(QN_STATUS, GetAttrValueFor(connection_status)); + element.AddElement(con_status_element); + } + XmppReturnStatus status = engine()->SendStanza(&element); + + return status; +} + +size_t +XmppChatroomModuleImpl::GetChatroomMemberCount() { + return chatroom_jid_members_.size(); +} + +XmppReturnStatus +XmppChatroomModuleImpl::CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator) { + *enumerator = new XmppChatroomMemberEnumeratorImpl(&chatroom_jid_members_, &chatroom_jid_members_version_); + return XMPP_RETURN_OK; +} + +const std::string +XmppChatroomModuleImpl::subject() { + return ""; //NYI +} + +XmppReturnStatus +XmppChatroomModuleImpl::SendMessage(const XmlElement& message) { + XmppReturnStatus xmpp_status = XMPP_RETURN_OK; + + // can only send a message if we're in the room + if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) { + return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code? + } + + if (message.Name() != QN_MESSAGE) { + IFR(XMPP_RETURN_BADARGUMENT); + } + + const std::string& type = message.Attr(QN_TYPE); + if (type != "groupchat") { + IFR(XMPP_RETURN_BADARGUMENT); + } + + if (message.HasAttr(QN_FROM)) { + IFR(XMPP_RETURN_BADARGUMENT); + } + + if (message.Attr(QN_TO) != chatroom_jid_.Str()) { + IFR(XMPP_RETURN_BADARGUMENT); + } + + IFR(engine()->SendStanza(&message)); + + return xmpp_status; +} + +enum TransitionType { + TRANSITION_TYPE_NONE = 0, + TRANSITION_TYPE_ENTER_SUCCESS = 1, + TRANSITION_TYPE_ENTER_FAILURE = 2, + TRANSITION_TYPE_EXIT_VOLUNTARILY = 3, + TRANSITION_TYPE_EXIT_INVOLUNTARILY = 4, +}; + +struct StateTransitionDescription { + XmppChatroomState old_state; + XmppChatroomState new_state; + bool is_valid_server_transition; + bool is_valid_client_transition; + TransitionType transition_type; +}; + +StateTransitionDescription Transitions[] = { + { XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, true, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_IN_ROOM, false, false, TRANSITION_TYPE_ENTER_SUCCESS, }, + { XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, false, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_ENTER_FAILURE, }, + { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_IN_ROOM, true, false, TRANSITION_TYPE_ENTER_SUCCESS, }, + { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, false, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_EXIT_INVOLUNTARILY, }, + { XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, true, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_EXIT_VOLUNTARILY, }, + { XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_IN_ROOM, false, false, TRANSITION_TYPE_NONE, }, +}; + + + +void +XmppChatroomModuleImpl::FireEnteredStatus(const XmlElement* presence, + XmppChatroomEnteredStatus status) { + if (chatroom_handler_) { + std::unique_ptr xmpp_presence(XmppPresence::Create()); + xmpp_presence->set_raw_xml(presence); + chatroom_handler_->ChatroomEnteredStatus(this, xmpp_presence.get(), status); + } +} + +void +XmppChatroomModuleImpl::FireExitStatus(XmppChatroomExitedStatus status) { + if (chatroom_handler_) + chatroom_handler_->ChatroomExitedStatus(this, status); +} + +void +XmppChatroomModuleImpl::FireMessageReceived(const XmlElement& message) { + if (chatroom_handler_) + chatroom_handler_->MessageReceived(this, message); +} + +void +XmppChatroomModuleImpl::FireMemberEntered(const XmppChatroomMember* entered_member) { + if (chatroom_handler_) + chatroom_handler_->MemberEntered(this, entered_member); +} + +void +XmppChatroomModuleImpl::FireMemberChanged( + const XmppChatroomMember* changed_member) { + if (chatroom_handler_) + chatroom_handler_->MemberChanged(this, changed_member); +} + +void +XmppChatroomModuleImpl::FireMemberExited(const XmppChatroomMember* exited_member) { + if (chatroom_handler_) + chatroom_handler_->MemberExited(this, exited_member); +} + + +XmppReturnStatus +XmppChatroomModuleImpl::ServerChangedOtherPresence(const XmlElement& + presence_element) { + XmppReturnStatus xmpp_status = XMPP_RETURN_OK; + std::unique_ptr presence(XmppPresence::Create()); + IFR(presence->set_raw_xml(&presence_element)); + + JidMemberMap::iterator pos = chatroom_jid_members_.find(presence->jid()); + + if (pos == chatroom_jid_members_.end()) { + if (presence->available() == XMPP_PRESENCE_AVAILABLE) { + XmppChatroomMemberImpl* member = new XmppChatroomMemberImpl(); + member->SetPresence(presence.get()); + chatroom_jid_members_.insert(std::make_pair(member->member_jid(), member)); + chatroom_jid_members_version_++; + FireMemberEntered(member); + } + } else { + XmppChatroomMemberImpl* member = pos->second; + if (presence->available() == XMPP_PRESENCE_AVAILABLE) { + member->SetPresence(presence.get()); + chatroom_jid_members_version_++; + FireMemberChanged(member); + } + else if (presence->available() == XMPP_PRESENCE_UNAVAILABLE) { + member->SetPresence(presence.get()); + chatroom_jid_members_.erase(pos); + chatroom_jid_members_version_++; + FireMemberExited(member); + delete member; + } + } + + return xmpp_status; +} + +XmppReturnStatus +XmppChatroomModuleImpl::ClientChangeMyPresence(XmppChatroomState new_state) { + return ChangePresence(new_state, NULL, false); +} + +XmppReturnStatus +XmppChatroomModuleImpl::ServerChangeMyPresence(const XmlElement& presence) { + XmppChatroomState new_state; + + if (presence.HasAttr(QN_TYPE) == false) { + new_state = XMPP_CHATROOM_STATE_IN_ROOM; + } else { + new_state = XMPP_CHATROOM_STATE_NOT_IN_ROOM; + } + return ChangePresence(new_state, &presence, true); + +} + +XmppReturnStatus +XmppChatroomModuleImpl::ChangePresence(XmppChatroomState new_state, + const XmlElement* presence, + bool isServer) { + RTC_UNUSED(presence); + + XmppChatroomState old_state = chatroom_state_; + + // do nothing if state hasn't changed + if (old_state == new_state) + return XMPP_RETURN_OK; + + // find the right transition description + StateTransitionDescription* transition_desc = NULL; + for (size_t i = 0; i < arraysize(Transitions); i++) { + if (Transitions[i].old_state == old_state && + Transitions[i].new_state == new_state) { + transition_desc = &Transitions[i]; + break; + } + } + + if (transition_desc == NULL) { + ASSERT(0); + return XMPP_RETURN_BADSTATE; + } + + // we assert for any invalid transition states, and we'll + if (isServer) { + // $TODO send original stanza back to server and log an error? + // Disable the assert because of b/6133072 + // ASSERT(transition_desc->is_valid_server_transition); + if (!transition_desc->is_valid_server_transition) { + return XMPP_RETURN_BADSTATE; + } + } else { + if (transition_desc->is_valid_client_transition == false) { + ASSERT(0); + return XMPP_RETURN_BADARGUMENT; + } + } + + // set the new state and then fire any notifications to the handler + chatroom_state_ = new_state; + + switch (transition_desc->transition_type) { + case TRANSITION_TYPE_ENTER_SUCCESS: + FireEnteredStatus(presence, XMPP_CHATROOM_ENTERED_SUCCESS); + break; + case TRANSITION_TYPE_ENTER_FAILURE: + FireEnteredStatus(presence, GetEnterFailureFromXml(presence)); + break; + case TRANSITION_TYPE_EXIT_INVOLUNTARILY: + FireExitStatus(GetExitFailureFromXml(presence)); + break; + case TRANSITION_TYPE_EXIT_VOLUNTARILY: + FireExitStatus(XMPP_CHATROOM_EXITED_REQUESTED); + break; + case TRANSITION_TYPE_NONE: + break; + } + + return XMPP_RETURN_OK; +} + +XmppChatroomEnteredStatus +XmppChatroomModuleImpl::GetEnterFailureFromXml(const XmlElement* presence) { + XmppChatroomEnteredStatus status = XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED; + const XmlElement* error = presence->FirstNamed(QN_ERROR); + if (error != NULL && error->HasAttr(QN_CODE)) { + int code = atoi(error->Attr(QN_CODE).c_str()); + switch (code) { + case 401: status = XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED; break; + case 403: { + status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED; + if (error->FirstNamed(QN_GOOGLE_SESSION_BLOCKED)) { + status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKED; + } else if (error->FirstNamed(QN_GOOGLE_SESSION_BLOCKING)) { + status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKING; + } + break; + } + case 405: status = XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED; break; + case 406: status = XMPP_CHATROOM_ENTERED_FAILURE_OUTDATED_CLIENT; break; + case 407: status = XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER; break; + case 409: status = XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT; break; + // http://xmpp.org/extensions/xep-0045.html#enter-maxusers + case 503: status = XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS; break; + } + } + return status; +} + +XmppChatroomExitedStatus +XmppChatroomModuleImpl::GetExitFailureFromXml(const XmlElement* presence) { + XmppChatroomExitedStatus status = XMPP_CHATROOM_EXITED_UNSPECIFIED; + const XmlElement* muc_user = presence->FirstNamed(QN_MUC_USER_X); + if (muc_user != NULL) { + const XmlElement* user_status = muc_user->FirstNamed(QN_MUC_USER_STATUS); + if (user_status != NULL && user_status->HasAttr(QN_CODE)) { + int code = atoi(user_status->Attr(QN_CODE).c_str()); + switch (code) { + case 307: status = XMPP_CHATROOM_EXITED_KICKED; break; + case 322: status = XMPP_CHATROOM_EXITED_NOT_A_MEMBER; break; + case 332: status = XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN; break; + } + } + } + return status; +} + +XmppReturnStatus +XmppChatroomMemberImpl::SetPresence(const XmppPresence* presence) { + ASSERT(presence != NULL); + + // copy presence + presence_.reset(XmppPresence::Create()); + presence_->set_raw_xml(presence->raw_xml()); + return XMPP_RETURN_OK; +} + +const Jid +XmppChatroomMemberImpl::member_jid() const { + return presence_->jid(); +} + +const Jid +XmppChatroomMemberImpl::full_jid() const { + return Jid(""); +} + +const std::string +XmppChatroomMemberImpl::name() const { + return member_jid().resource(); +} + +const XmppPresence* +XmppChatroomMemberImpl::presence() const { + return presence_.get(); +} + + +// XmppChatroomMemberEnumeratorImpl -------------------------------------- +XmppChatroomMemberEnumeratorImpl::XmppChatroomMemberEnumeratorImpl( + XmppChatroomModuleImpl::JidMemberMap* map, int* map_version) { + map_ = map; + map_version_ = map_version; + map_version_created_ = *map_version_; + iterator_ = map->begin(); + before_beginning_ = true; +} + +XmppChatroomMember* +XmppChatroomMemberEnumeratorImpl::current() { + if (IsValid() == false) { + return NULL; + } else if (IsBeforeBeginning() || IsAfterEnd()) { + return NULL; + } else { + return iterator_->second; + } +} + +bool +XmppChatroomMemberEnumeratorImpl::Prev() { + if (IsValid() == false) { + return false; + } else if (IsBeforeBeginning()) { + return false; + } else if (iterator_ == map_->begin()) { + before_beginning_ = true; + return false; + } else { + iterator_--; + return current() != NULL; + } +} + +bool +XmppChatroomMemberEnumeratorImpl::Next() { + if (IsValid() == false) { + return false; + } else if (IsBeforeBeginning()) { + before_beginning_ = false; + iterator_ = map_->begin(); + return current() != NULL; + } else if (IsAfterEnd()) { + return false; + } else { + iterator_++; + return current() != NULL; + } +} + +bool +XmppChatroomMemberEnumeratorImpl::IsValid() { + return map_version_created_ == *map_version_; +} + +bool +XmppChatroomMemberEnumeratorImpl::IsBeforeBeginning() { + return before_beginning_; +} + +bool +XmppChatroomMemberEnumeratorImpl::IsAfterEnd() { + return (iterator_ == map_->end()); +} + + + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/constants.cc b/webrtc/libjingle/xmpp/constants.cc new file mode 100644 index 0000000000..6ad2a5aa37 --- /dev/null +++ b/webrtc/libjingle/xmpp/constants.cc @@ -0,0 +1,613 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/constants.h" + +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/jid.h" + +namespace buzz { + +// TODO: Remove static objects of complex types, particularly +// Jid and QName. + +const char NS_CLIENT[] = "jabber:client"; +const char NS_SERVER[] = "jabber:server"; +const char NS_STREAM[] = "http://etherx.jabber.org/streams"; +const char NS_XSTREAM[] = "urn:ietf:params:xml:ns:xmpp-streams"; +const char NS_TLS[] = "urn:ietf:params:xml:ns:xmpp-tls"; +const char NS_SASL[] = "urn:ietf:params:xml:ns:xmpp-sasl"; +const char NS_BIND[] = "urn:ietf:params:xml:ns:xmpp-bind"; +const char NS_DIALBACK[] = "jabber:server:dialback"; +const char NS_SESSION[] = "urn:ietf:params:xml:ns:xmpp-session"; +const char NS_STANZA[] = "urn:ietf:params:xml:ns:xmpp-stanzas"; +const char NS_PRIVACY[] = "jabber:iq:privacy"; +const char NS_ROSTER[] = "jabber:iq:roster"; +const char NS_VCARD[] = "vcard-temp"; +const char NS_AVATAR_HASH[] = "google:avatar"; +const char NS_VCARD_UPDATE[] = "vcard-temp:x:update"; +const char STR_CLIENT[] = "client"; +const char STR_SERVER[] = "server"; +const char STR_STREAM[] = "stream"; + +const char STR_GET[] = "get"; +const char STR_SET[] = "set"; +const char STR_RESULT[] = "result"; +const char STR_ERROR[] = "error"; + +const char STR_FORM[] = "form"; +const char STR_SUBMIT[] = "submit"; +const char STR_TEXT_SINGLE[] = "text-single"; +const char STR_LIST_SINGLE[] = "list-single"; +const char STR_LIST_MULTI[] = "list-multi"; +const char STR_HIDDEN[] = "hidden"; +const char STR_FORM_TYPE[] = "FORM_TYPE"; + +const char STR_FROM[] = "from"; +const char STR_TO[] = "to"; +const char STR_BOTH[] = "both"; +const char STR_REMOVE[] = "remove"; +const char STR_TRUE[] = "true"; + +const char STR_TYPE[] = "type"; +const char STR_NAME[] = "name"; +const char STR_ID[] = "id"; +const char STR_JID[] = "jid"; +const char STR_SUBSCRIPTION[] = "subscription"; +const char STR_ASK[] = "ask"; +const char STR_X[] = "x"; +const char STR_GOOGLE_COM[] = "google.com"; +const char STR_GMAIL_COM[] = "gmail.com"; +const char STR_GOOGLEMAIL_COM[] = "googlemail.com"; +const char STR_DEFAULT_DOMAIN[] = "default.talk.google.com"; +const char STR_TALK_GOOGLE_COM[] = "talk.google.com"; +const char STR_TALKX_L_GOOGLE_COM[] = "talkx.l.google.com"; +const char STR_XMPP_GOOGLE_COM[] = "xmpp.google.com"; +const char STR_XMPPX_L_GOOGLE_COM[] = "xmppx.l.google.com"; + +#ifdef FEATURE_ENABLE_VOICEMAIL +const char STR_VOICEMAIL[] = "voicemail"; +const char STR_OUTGOINGVOICEMAIL[] = "outgoingvoicemail"; +#endif + +const char STR_UNAVAILABLE[] = "unavailable"; + +const char NS_PING[] = "urn:xmpp:ping"; +const StaticQName QN_PING = { NS_PING, "ping" }; + +const char NS_MUC_UNIQUE[] = "http://jabber.org/protocol/muc#unique"; +const StaticQName QN_MUC_UNIQUE_QUERY = { NS_MUC_UNIQUE, "unique" }; +const StaticQName QN_HANGOUT_ID = { STR_EMPTY, "hangout-id" }; + +const char STR_GOOGLE_MUC_LOOKUP_JID[] = "lookup.groupchat.google.com"; + +const char STR_MUC_ROOMCONFIG_ROOMNAME[] = "muc#roomconfig_roomname"; +const char STR_MUC_ROOMCONFIG_FEATURES[] = "muc#roomconfig_features"; +const char STR_MUC_ROOM_FEATURE_ENTERPRISE[] = "muc_enterprise"; +const char STR_MUC_ROOMCONFIG[] = "http://jabber.org/protocol/muc#roomconfig"; +const char STR_MUC_ROOM_FEATURE_HANGOUT[] = "muc_es"; +const char STR_MUC_ROOM_FEATURE_HANGOUT_LITE[] = "muc_lite"; +const char STR_MUC_ROOM_FEATURE_BROADCAST[] = "broadcast"; +const char STR_MUC_ROOM_FEATURE_MULTI_USER_VC[] = "muc_muvc"; +const char STR_MUC_ROOM_FEATURE_RECORDABLE[] = "recordable"; +const char STR_MUC_ROOM_FEATURE_CUSTOM_RECORDING[] = "custom_recording"; +const char STR_MUC_ROOM_OWNER_PROFILE_ID[] = "muc#roominfo_owner_profile_id"; +const char STR_MUC_ROOM_FEATURE_ABUSE_RECORDABLE[] = "abuse_recordable"; + +const char STR_ID_TYPE_CONVERSATION[] = "conversation"; +const char NS_GOOGLE_MUC_HANGOUT[] = "google:muc#hangout"; +const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE = + { NS_GOOGLE_MUC_HANGOUT, "invite" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE_TYPE = + { NS_GOOGLE_MUC_HANGOUT, "invite-type" }; +const StaticQName QN_ATTR_CREATE_ACTIVITY = + { STR_EMPTY, "create-activity" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_PUBLIC = + { NS_GOOGLE_MUC_HANGOUT, "public" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITEE = + { NS_GOOGLE_MUC_HANGOUT, "invitee" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_STATUS = + { NS_GOOGLE_MUC_HANGOUT, "notification-status" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_TYPE = { + NS_GOOGLE_MUC_HANGOUT, "notification-type" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_HANGOUT_START_CONTEXT = { + NS_GOOGLE_MUC_HANGOUT, "hangout-start-context" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_CONVERSATION_ID = { + NS_GOOGLE_MUC_HANGOUT, "conversation-id" }; + +const StaticQName QN_STREAM_STREAM = { NS_STREAM, STR_STREAM }; +const StaticQName QN_STREAM_FEATURES = { NS_STREAM, "features" }; +const StaticQName QN_STREAM_ERROR = { NS_STREAM, "error" }; + +const StaticQName QN_XSTREAM_BAD_FORMAT = { NS_XSTREAM, "bad-format" }; +const StaticQName QN_XSTREAM_BAD_NAMESPACE_PREFIX = + { NS_XSTREAM, "bad-namespace-prefix" }; +const StaticQName QN_XSTREAM_CONFLICT = { NS_XSTREAM, "conflict" }; +const StaticQName QN_XSTREAM_CONNECTION_TIMEOUT = + { NS_XSTREAM, "connection-timeout" }; +const StaticQName QN_XSTREAM_HOST_GONE = { NS_XSTREAM, "host-gone" }; +const StaticQName QN_XSTREAM_HOST_UNKNOWN = { NS_XSTREAM, "host-unknown" }; +const StaticQName QN_XSTREAM_IMPROPER_ADDRESSIING = + { NS_XSTREAM, "improper-addressing" }; +const StaticQName QN_XSTREAM_INTERNAL_SERVER_ERROR = + { NS_XSTREAM, "internal-server-error" }; +const StaticQName QN_XSTREAM_INVALID_FROM = { NS_XSTREAM, "invalid-from" }; +const StaticQName QN_XSTREAM_INVALID_ID = { NS_XSTREAM, "invalid-id" }; +const StaticQName QN_XSTREAM_INVALID_NAMESPACE = + { NS_XSTREAM, "invalid-namespace" }; +const StaticQName QN_XSTREAM_INVALID_XML = { NS_XSTREAM, "invalid-xml" }; +const StaticQName QN_XSTREAM_NOT_AUTHORIZED = { NS_XSTREAM, "not-authorized" }; +const StaticQName QN_XSTREAM_POLICY_VIOLATION = + { NS_XSTREAM, "policy-violation" }; +const StaticQName QN_XSTREAM_REMOTE_CONNECTION_FAILED = + { NS_XSTREAM, "remote-connection-failed" }; +const StaticQName QN_XSTREAM_RESOURCE_CONSTRAINT = + { NS_XSTREAM, "resource-constraint" }; +const StaticQName QN_XSTREAM_RESTRICTED_XML = { NS_XSTREAM, "restricted-xml" }; +const StaticQName QN_XSTREAM_SEE_OTHER_HOST = { NS_XSTREAM, "see-other-host" }; +const StaticQName QN_XSTREAM_SYSTEM_SHUTDOWN = + { NS_XSTREAM, "system-shutdown" }; +const StaticQName QN_XSTREAM_UNDEFINED_CONDITION = + { NS_XSTREAM, "undefined-condition" }; +const StaticQName QN_XSTREAM_UNSUPPORTED_ENCODING = + { NS_XSTREAM, "unsupported-encoding" }; +const StaticQName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE = + { NS_XSTREAM, "unsupported-stanza-type" }; +const StaticQName QN_XSTREAM_UNSUPPORTED_VERSION = + { NS_XSTREAM, "unsupported-version" }; +const StaticQName QN_XSTREAM_XML_NOT_WELL_FORMED = + { NS_XSTREAM, "xml-not-well-formed" }; +const StaticQName QN_XSTREAM_TEXT = { NS_XSTREAM, "text" }; + +const StaticQName QN_TLS_STARTTLS = { NS_TLS, "starttls" }; +const StaticQName QN_TLS_REQUIRED = { NS_TLS, "required" }; +const StaticQName QN_TLS_PROCEED = { NS_TLS, "proceed" }; +const StaticQName QN_TLS_FAILURE = { NS_TLS, "failure" }; + +const StaticQName QN_SASL_MECHANISMS = { NS_SASL, "mechanisms" }; +const StaticQName QN_SASL_MECHANISM = { NS_SASL, "mechanism" }; +const StaticQName QN_SASL_AUTH = { NS_SASL, "auth" }; +const StaticQName QN_SASL_CHALLENGE = { NS_SASL, "challenge" }; +const StaticQName QN_SASL_RESPONSE = { NS_SASL, "response" }; +const StaticQName QN_SASL_ABORT = { NS_SASL, "abort" }; +const StaticQName QN_SASL_SUCCESS = { NS_SASL, "success" }; +const StaticQName QN_SASL_FAILURE = { NS_SASL, "failure" }; +const StaticQName QN_SASL_ABORTED = { NS_SASL, "aborted" }; +const StaticQName QN_SASL_INCORRECT_ENCODING = + { NS_SASL, "incorrect-encoding" }; +const StaticQName QN_SASL_INVALID_AUTHZID = { NS_SASL, "invalid-authzid" }; +const StaticQName QN_SASL_INVALID_MECHANISM = { NS_SASL, "invalid-mechanism" }; +const StaticQName QN_SASL_MECHANISM_TOO_WEAK = + { NS_SASL, "mechanism-too-weak" }; +const StaticQName QN_SASL_NOT_AUTHORIZED = { NS_SASL, "not-authorized" }; +const StaticQName QN_SASL_TEMPORARY_AUTH_FAILURE = + { NS_SASL, "temporary-auth-failure" }; + +// These are non-standard. +const char NS_GOOGLE_AUTH_PROTOCOL[] = + "http://www.google.com/talk/protocol/auth"; +const StaticQName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT = + { NS_GOOGLE_AUTH_PROTOCOL, "client-uses-full-bind-result" }; +const StaticQName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN = + { NS_GOOGLE_AUTH_PROTOCOL, "allow-non-google-login" }; +const StaticQName QN_GOOGLE_AUTH_SERVICE = + { NS_GOOGLE_AUTH_PROTOCOL, "service" }; + +const StaticQName QN_DIALBACK_RESULT = { NS_DIALBACK, "result" }; +const StaticQName QN_DIALBACK_VERIFY = { NS_DIALBACK, "verify" }; + +const StaticQName QN_STANZA_BAD_REQUEST = { NS_STANZA, "bad-request" }; +const StaticQName QN_STANZA_CONFLICT = { NS_STANZA, "conflict" }; +const StaticQName QN_STANZA_FEATURE_NOT_IMPLEMENTED = + { NS_STANZA, "feature-not-implemented" }; +const StaticQName QN_STANZA_FORBIDDEN = { NS_STANZA, "forbidden" }; +const StaticQName QN_STANZA_GONE = { NS_STANZA, "gone" }; +const StaticQName QN_STANZA_INTERNAL_SERVER_ERROR = + { NS_STANZA, "internal-server-error" }; +const StaticQName QN_STANZA_ITEM_NOT_FOUND = { NS_STANZA, "item-not-found" }; +const StaticQName QN_STANZA_JID_MALFORMED = { NS_STANZA, "jid-malformed" }; +const StaticQName QN_STANZA_NOT_ACCEPTABLE = { NS_STANZA, "not-acceptable" }; +const StaticQName QN_STANZA_NOT_ALLOWED = { NS_STANZA, "not-allowed" }; +const StaticQName QN_STANZA_PAYMENT_REQUIRED = + { NS_STANZA, "payment-required" }; +const StaticQName QN_STANZA_RECIPIENT_UNAVAILABLE = + { NS_STANZA, "recipient-unavailable" }; +const StaticQName QN_STANZA_REDIRECT = { NS_STANZA, "redirect" }; +const StaticQName QN_STANZA_REGISTRATION_REQUIRED = + { NS_STANZA, "registration-required" }; +const StaticQName QN_STANZA_REMOTE_SERVER_NOT_FOUND = + { NS_STANZA, "remote-server-not-found" }; +const StaticQName QN_STANZA_REMOTE_SERVER_TIMEOUT = + { NS_STANZA, "remote-server-timeout" }; +const StaticQName QN_STANZA_RESOURCE_CONSTRAINT = + { NS_STANZA, "resource-constraint" }; +const StaticQName QN_STANZA_SERVICE_UNAVAILABLE = + { NS_STANZA, "service-unavailable" }; +const StaticQName QN_STANZA_SUBSCRIPTION_REQUIRED = + { NS_STANZA, "subscription-required" }; +const StaticQName QN_STANZA_UNDEFINED_CONDITION = + { NS_STANZA, "undefined-condition" }; +const StaticQName QN_STANZA_UNEXPECTED_REQUEST = + { NS_STANZA, "unexpected-request" }; +const StaticQName QN_STANZA_TEXT = { NS_STANZA, "text" }; + +const StaticQName QN_BIND_BIND = { NS_BIND, "bind" }; +const StaticQName QN_BIND_RESOURCE = { NS_BIND, "resource" }; +const StaticQName QN_BIND_JID = { NS_BIND, "jid" }; + +const StaticQName QN_MESSAGE = { NS_CLIENT, "message" }; +const StaticQName QN_BODY = { NS_CLIENT, "body" }; +const StaticQName QN_SUBJECT = { NS_CLIENT, "subject" }; +const StaticQName QN_THREAD = { NS_CLIENT, "thread" }; +const StaticQName QN_PRESENCE = { NS_CLIENT, "presence" }; +const StaticQName QN_SHOW = { NS_CLIENT, "show" }; +const StaticQName QN_STATUS = { NS_CLIENT, "status" }; +const StaticQName QN_LANG = { NS_CLIENT, "lang" }; +const StaticQName QN_PRIORITY = { NS_CLIENT, "priority" }; +const StaticQName QN_IQ = { NS_CLIENT, "iq" }; +const StaticQName QN_ERROR = { NS_CLIENT, "error" }; + +const StaticQName QN_SERVER_MESSAGE = { NS_SERVER, "message" }; +const StaticQName QN_SERVER_BODY = { NS_SERVER, "body" }; +const StaticQName QN_SERVER_SUBJECT = { NS_SERVER, "subject" }; +const StaticQName QN_SERVER_THREAD = { NS_SERVER, "thread" }; +const StaticQName QN_SERVER_PRESENCE = { NS_SERVER, "presence" }; +const StaticQName QN_SERVER_SHOW = { NS_SERVER, "show" }; +const StaticQName QN_SERVER_STATUS = { NS_SERVER, "status" }; +const StaticQName QN_SERVER_LANG = { NS_SERVER, "lang" }; +const StaticQName QN_SERVER_PRIORITY = { NS_SERVER, "priority" }; +const StaticQName QN_SERVER_IQ = { NS_SERVER, "iq" }; +const StaticQName QN_SERVER_ERROR = { NS_SERVER, "error" }; + +const StaticQName QN_SESSION_SESSION = { NS_SESSION, "session" }; + +const StaticQName QN_PRIVACY_QUERY = { NS_PRIVACY, "query" }; +const StaticQName QN_PRIVACY_ACTIVE = { NS_PRIVACY, "active" }; +const StaticQName QN_PRIVACY_DEFAULT = { NS_PRIVACY, "default" }; +const StaticQName QN_PRIVACY_LIST = { NS_PRIVACY, "list" }; +const StaticQName QN_PRIVACY_ITEM = { NS_PRIVACY, "item" }; +const StaticQName QN_PRIVACY_IQ = { NS_PRIVACY, "iq" }; +const StaticQName QN_PRIVACY_MESSAGE = { NS_PRIVACY, "message" }; +const StaticQName QN_PRIVACY_PRESENCE_IN = { NS_PRIVACY, "presence-in" }; +const StaticQName QN_PRIVACY_PRESENCE_OUT = { NS_PRIVACY, "presence-out" }; + +const StaticQName QN_ROSTER_QUERY = { NS_ROSTER, "query" }; +const StaticQName QN_ROSTER_ITEM = { NS_ROSTER, "item" }; +const StaticQName QN_ROSTER_GROUP = { NS_ROSTER, "group" }; + +const StaticQName QN_VCARD = { NS_VCARD, "vCard" }; +const StaticQName QN_VCARD_FN = { NS_VCARD, "FN" }; +const StaticQName QN_VCARD_PHOTO = { NS_VCARD, "PHOTO" }; +const StaticQName QN_VCARD_PHOTO_BINVAL = { NS_VCARD, "BINVAL" }; +const StaticQName QN_VCARD_AVATAR_HASH = { NS_AVATAR_HASH, "hash" }; +const StaticQName QN_VCARD_AVATAR_HASH_MODIFIED = + { NS_AVATAR_HASH, "modified" }; + +const StaticQName QN_NAME = { STR_EMPTY, "name" }; +const StaticQName QN_AFFILIATION = { STR_EMPTY, "affiliation" }; +const StaticQName QN_ROLE = { STR_EMPTY, "role" }; + +#if defined(FEATURE_ENABLE_PSTN) +const StaticQName QN_VCARD_TEL = { NS_VCARD, "TEL" }; +const StaticQName QN_VCARD_VOICE = { NS_VCARD, "VOICE" }; +const StaticQName QN_VCARD_HOME = { NS_VCARD, "HOME" }; +const StaticQName QN_VCARD_WORK = { NS_VCARD, "WORK" }; +const StaticQName QN_VCARD_CELL = { NS_VCARD, "CELL" }; +const StaticQName QN_VCARD_NUMBER = { NS_VCARD, "NUMBER" }; +#endif + +const StaticQName QN_XML_LANG = { NS_XML, "lang" }; + +const StaticQName QN_ENCODING = { STR_EMPTY, STR_ENCODING }; +const StaticQName QN_VERSION = { STR_EMPTY, STR_VERSION }; +const StaticQName QN_TO = { STR_EMPTY, "to" }; +const StaticQName QN_FROM = { STR_EMPTY, "from" }; +const StaticQName QN_TYPE = { STR_EMPTY, "type" }; +const StaticQName QN_ID = { STR_EMPTY, "id" }; +const StaticQName QN_CODE = { STR_EMPTY, "code" }; + +const StaticQName QN_VALUE = { STR_EMPTY, "value" }; +const StaticQName QN_ACTION = { STR_EMPTY, "action" }; +const StaticQName QN_ORDER = { STR_EMPTY, "order" }; +const StaticQName QN_MECHANISM = { STR_EMPTY, "mechanism" }; +const StaticQName QN_ASK = { STR_EMPTY, "ask" }; +const StaticQName QN_JID = { STR_EMPTY, "jid" }; +const StaticQName QN_NICK = { STR_EMPTY, "nick" }; +const StaticQName QN_SUBSCRIPTION = { STR_EMPTY, "subscription" }; +const StaticQName QN_TITLE1 = { STR_EMPTY, "title1" }; +const StaticQName QN_TITLE2 = { STR_EMPTY, "title2" }; + +const StaticQName QN_XMLNS_CLIENT = { NS_XMLNS, STR_CLIENT }; +const StaticQName QN_XMLNS_SERVER = { NS_XMLNS, STR_SERVER }; +const StaticQName QN_XMLNS_STREAM = { NS_XMLNS, STR_STREAM }; + + +// Presence +const char STR_SHOW_AWAY[] = "away"; +const char STR_SHOW_CHAT[] = "chat"; +const char STR_SHOW_DND[] = "dnd"; +const char STR_SHOW_XA[] = "xa"; +const char STR_SHOW_OFFLINE[] = "offline"; + +const char NS_GOOGLE_PSTN_CONFERENCE[] = "http://www.google.com/pstn-conference"; +const StaticQName QN_GOOGLE_PSTN_CONFERENCE_STATUS = { NS_GOOGLE_PSTN_CONFERENCE, "status" }; +const StaticQName QN_ATTR_STATUS = { STR_EMPTY, "status" }; + +// Presence connection status +const char STR_PSTN_CONFERENCE_STATUS_CONNECTING[] = "connecting"; +const char STR_PSTN_CONFERENCE_STATUS_JOINING[] = "joining"; +const char STR_PSTN_CONFERENCE_STATUS_CONNECTED[] = "connected"; +const char STR_PSTN_CONFERENCE_STATUS_HANGUP[] = "hangup"; + +// Subscription +const char STR_SUBSCRIBE[] = "subscribe"; +const char STR_SUBSCRIBED[] = "subscribed"; +const char STR_UNSUBSCRIBE[] = "unsubscribe"; +const char STR_UNSUBSCRIBED[] = "unsubscribed"; + +// Google Invite +const char NS_GOOGLE_SUBSCRIBE[] = "google:subscribe"; +const StaticQName QN_INVITATION = { NS_GOOGLE_SUBSCRIBE, "invitation" }; +const StaticQName QN_INVITE_NAME = { NS_GOOGLE_SUBSCRIBE, "name" }; +const StaticQName QN_INVITE_SUBJECT = { NS_GOOGLE_SUBSCRIBE, "subject" }; +const StaticQName QN_INVITE_MESSAGE = { NS_GOOGLE_SUBSCRIBE, "body" }; + +// Kick +const char NS_GOOGLE_MUC_ADMIN[] = "google:muc#admin"; +const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY = { NS_GOOGLE_MUC_ADMIN, "query" }; +const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM = + { NS_GOOGLE_MUC_ADMIN, "item" }; +const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM_REASON = + { NS_GOOGLE_MUC_ADMIN, "reason" }; + +// PubSub: http://xmpp.org/extensions/xep-0060.html +const char NS_PUBSUB[] = "http://jabber.org/protocol/pubsub"; +const StaticQName QN_PUBSUB = { NS_PUBSUB, "pubsub" }; +const StaticQName QN_PUBSUB_ITEMS = { NS_PUBSUB, "items" }; +const StaticQName QN_PUBSUB_ITEM = { NS_PUBSUB, "item" }; +const StaticQName QN_PUBSUB_PUBLISH = { NS_PUBSUB, "publish" }; +const StaticQName QN_PUBSUB_RETRACT = { NS_PUBSUB, "retract" }; +const StaticQName QN_ATTR_PUBLISHER = { STR_EMPTY, "publisher" }; + +const char NS_PUBSUB_EVENT[] = "http://jabber.org/protocol/pubsub#event"; +const StaticQName QN_NODE = { STR_EMPTY, "node" }; +const StaticQName QN_PUBSUB_EVENT = { NS_PUBSUB_EVENT, "event" }; +const StaticQName QN_PUBSUB_EVENT_ITEMS = { NS_PUBSUB_EVENT, "items" }; +const StaticQName QN_PUBSUB_EVENT_ITEM = { NS_PUBSUB_EVENT, "item" }; +const StaticQName QN_PUBSUB_EVENT_RETRACT = { NS_PUBSUB_EVENT, "retract" }; +const StaticQName QN_NOTIFY = { STR_EMPTY, "notify" }; + +const char NS_PRESENTER[] = "google:presenter"; +const StaticQName QN_PRESENTER_PRESENTER = { NS_PRESENTER, "presenter" }; +const StaticQName QN_PRESENTER_PRESENTATION_ITEM = + { NS_PRESENTER, "presentation-item" }; +const StaticQName QN_PRESENTER_PRESENTATION_TYPE = + { NS_PRESENTER, "presentation-type" }; +const StaticQName QN_PRESENTER_PRESENTATION_ID = + { NS_PRESENTER, "presentation-id" }; + +// JEP 0030 +const StaticQName QN_CATEGORY = { STR_EMPTY, "category" }; +const StaticQName QN_VAR = { STR_EMPTY, "var" }; +const char NS_DISCO_INFO[] = "http://jabber.org/protocol/disco#info"; +const char NS_DISCO_ITEMS[] = "http://jabber.org/protocol/disco#items"; +const StaticQName QN_DISCO_INFO_QUERY = { NS_DISCO_INFO, "query" }; +const StaticQName QN_DISCO_IDENTITY = { NS_DISCO_INFO, "identity" }; +const StaticQName QN_DISCO_FEATURE = { NS_DISCO_INFO, "feature" }; + +const StaticQName QN_DISCO_ITEMS_QUERY = { NS_DISCO_ITEMS, "query" }; +const StaticQName QN_DISCO_ITEM = { NS_DISCO_ITEMS, "item" }; + +// JEP 0020 +const char NS_FEATURE[] = "http://jabber.org/protocol/feature-neg"; +const StaticQName QN_FEATURE_FEATURE = { NS_FEATURE, "feature" }; + +// JEP 0004 +const char NS_XDATA[] = "jabber:x:data"; +const StaticQName QN_XDATA_X = { NS_XDATA, "x" }; +const StaticQName QN_XDATA_INSTRUCTIONS = { NS_XDATA, "instructions" }; +const StaticQName QN_XDATA_TITLE = { NS_XDATA, "title" }; +const StaticQName QN_XDATA_FIELD = { NS_XDATA, "field" }; +const StaticQName QN_XDATA_REPORTED = { NS_XDATA, "reported" }; +const StaticQName QN_XDATA_ITEM = { NS_XDATA, "item" }; +const StaticQName QN_XDATA_DESC = { NS_XDATA, "desc" }; +const StaticQName QN_XDATA_REQUIRED = { NS_XDATA, "required" }; +const StaticQName QN_XDATA_VALUE = { NS_XDATA, "value" }; +const StaticQName QN_XDATA_OPTION = { NS_XDATA, "option" }; + +// JEP 0045 +const char NS_MUC[] = "http://jabber.org/protocol/muc"; +const StaticQName QN_MUC_X = { NS_MUC, "x" }; +const StaticQName QN_MUC_ITEM = { NS_MUC, "item" }; +const StaticQName QN_MUC_AFFILIATION = { NS_MUC, "affiliation" }; +const StaticQName QN_MUC_ROLE = { NS_MUC, "role" }; +const char STR_AFFILIATION_NONE[] = "none"; +const char STR_ROLE_PARTICIPANT[] = "participant"; + +const char NS_GOOGLE_SESSION[] = "http://www.google.com/session"; +const StaticQName QN_GOOGLE_CIRCLE_ID = { STR_EMPTY, "google-circle-id" }; +const StaticQName QN_GOOGLE_USER_ID = { STR_EMPTY, "google-user-id" }; +const StaticQName QN_GOOGLE_SESSION_BLOCKED = { NS_GOOGLE_SESSION, "blocked" }; +const StaticQName QN_GOOGLE_SESSION_BLOCKING = + { NS_GOOGLE_SESSION, "blocking" }; + +const char NS_MUC_OWNER[] = "http://jabber.org/protocol/muc#owner"; +const StaticQName QN_MUC_OWNER_QUERY = { NS_MUC_OWNER, "query" }; + +const char NS_MUC_USER[] = "http://jabber.org/protocol/muc#user"; +const StaticQName QN_MUC_USER_CONTINUE = { NS_MUC_USER, "continue" }; +const StaticQName QN_MUC_USER_X = { NS_MUC_USER, "x" }; +const StaticQName QN_MUC_USER_ITEM = { NS_MUC_USER, "item" }; +const StaticQName QN_MUC_USER_STATUS = { NS_MUC_USER, "status" }; +const StaticQName QN_MUC_USER_REASON = { NS_MUC_USER, "reason" }; +const StaticQName QN_MUC_USER_ABUSE_VIOLATION = { NS_MUC_USER, "abuse-violation" }; + +// JEP 0055 - Jabber Search +const char NS_SEARCH[] = "jabber:iq:search"; +const StaticQName QN_SEARCH_QUERY = { NS_SEARCH, "query" }; +const StaticQName QN_SEARCH_ITEM = { NS_SEARCH, "item" }; +const StaticQName QN_SEARCH_ROOM_NAME = { NS_SEARCH, "room-name" }; +const StaticQName QN_SEARCH_ROOM_DOMAIN = { NS_SEARCH, "room-domain" }; +const StaticQName QN_SEARCH_ROOM_JID = { NS_SEARCH, "room-jid" }; +const StaticQName QN_SEARCH_HANGOUT_ID = { NS_SEARCH, "hangout-id" }; +const StaticQName QN_SEARCH_EXTERNAL_ID = { NS_SEARCH, "external-id" }; + +// JEP 0115 +const char NS_CAPS[] = "http://jabber.org/protocol/caps"; +const StaticQName QN_CAPS_C = { NS_CAPS, "c" }; +const StaticQName QN_VER = { STR_EMPTY, "ver" }; +const StaticQName QN_EXT = { STR_EMPTY, "ext" }; + +// JEP 0153 +const char kNSVCard[] = "vcard-temp:x:update"; +const StaticQName kQnVCardX = { kNSVCard, "x" }; +const StaticQName kQnVCardPhoto = { kNSVCard, "photo" }; + +// JEP 0172 User Nickname +const char NS_NICKNAME[] = "http://jabber.org/protocol/nick"; +const StaticQName QN_NICKNAME = { NS_NICKNAME, "nick" }; + +// JEP 0085 chat state +const char NS_CHATSTATE[] = "http://jabber.org/protocol/chatstates"; +const StaticQName QN_CS_ACTIVE = { NS_CHATSTATE, "active" }; +const StaticQName QN_CS_COMPOSING = { NS_CHATSTATE, "composing" }; +const StaticQName QN_CS_PAUSED = { NS_CHATSTATE, "paused" }; +const StaticQName QN_CS_INACTIVE = { NS_CHATSTATE, "inactive" }; +const StaticQName QN_CS_GONE = { NS_CHATSTATE, "gone" }; + +// JEP 0091 Delayed Delivery +const char kNSDelay[] = "jabber:x:delay"; +const StaticQName kQnDelayX = { kNSDelay, "x" }; +const StaticQName kQnStamp = { STR_EMPTY, "stamp" }; + +// Google time stamping (higher resolution) +const char kNSTimestamp[] = "google:timestamp"; +const StaticQName kQnTime = { kNSTimestamp, "time" }; +const StaticQName kQnMilliseconds = { STR_EMPTY, "ms" }; + +// Jingle Info +const char NS_JINGLE_INFO[] = "google:jingleinfo"; +const StaticQName QN_JINGLE_INFO_QUERY = { NS_JINGLE_INFO, "query" }; +const StaticQName QN_JINGLE_INFO_STUN = { NS_JINGLE_INFO, "stun" }; +const StaticQName QN_JINGLE_INFO_RELAY = { NS_JINGLE_INFO, "relay" }; +const StaticQName QN_JINGLE_INFO_SERVER = { NS_JINGLE_INFO, "server" }; +const StaticQName QN_JINGLE_INFO_TOKEN = { NS_JINGLE_INFO, "token" }; +const StaticQName QN_JINGLE_INFO_HOST = { STR_EMPTY, "host" }; +const StaticQName QN_JINGLE_INFO_TCP = { STR_EMPTY, "tcp" }; +const StaticQName QN_JINGLE_INFO_UDP = { STR_EMPTY, "udp" }; +const StaticQName QN_JINGLE_INFO_TCPSSL = { STR_EMPTY, "tcpssl" }; + +// Call Performance Logging +const char NS_GOOGLE_CALLPERF_STATS[] = "google:call-perf-stats"; +const StaticQName QN_CALLPERF_STATS = + { NS_GOOGLE_CALLPERF_STATS, "callPerfStats" }; +const StaticQName QN_CALLPERF_SESSIONID = { STR_EMPTY, "sessionId" }; +const StaticQName QN_CALLPERF_LOCALUSER = { STR_EMPTY, "localUser" }; +const StaticQName QN_CALLPERF_REMOTEUSER = { STR_EMPTY, "remoteUser" }; +const StaticQName QN_CALLPERF_STARTTIME = { STR_EMPTY, "startTime" }; +const StaticQName QN_CALLPERF_CALL_LENGTH = { STR_EMPTY, "callLength" }; +const StaticQName QN_CALLPERF_CALL_ACCEPTED = { STR_EMPTY, "callAccepted" }; +const StaticQName QN_CALLPERF_CALL_ERROR_CODE = { STR_EMPTY, "callErrorCode" }; +const StaticQName QN_CALLPERF_TERMINATE_CODE = { STR_EMPTY, "terminateCode" }; +const StaticQName QN_CALLPERF_DATAPOINT = + { NS_GOOGLE_CALLPERF_STATS, "dataPoint" }; +const StaticQName QN_CALLPERF_DATAPOINT_TIME = { STR_EMPTY, "timeStamp" }; +const StaticQName QN_CALLPERF_DATAPOINT_FRACTION_LOST = + { STR_EMPTY, "fraction_lost" }; +const StaticQName QN_CALLPERF_DATAPOINT_CUM_LOST = { STR_EMPTY, "cum_lost" }; +const StaticQName QN_CALLPERF_DATAPOINT_EXT_MAX = { STR_EMPTY, "ext_max" }; +const StaticQName QN_CALLPERF_DATAPOINT_JITTER = { STR_EMPTY, "jitter" }; +const StaticQName QN_CALLPERF_DATAPOINT_RTT = { STR_EMPTY, "RTT" }; +const StaticQName QN_CALLPERF_DATAPOINT_BYTES_R = + { STR_EMPTY, "bytesReceived" }; +const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_R = + { STR_EMPTY, "packetsReceived" }; +const StaticQName QN_CALLPERF_DATAPOINT_BYTES_S = { STR_EMPTY, "bytesSent" }; +const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_S = + { STR_EMPTY, "packetsSent" }; +const StaticQName QN_CALLPERF_DATAPOINT_PROCESS_CPU = + { STR_EMPTY, "processCpu" }; +const StaticQName QN_CALLPERF_DATAPOINT_SYSTEM_CPU = { STR_EMPTY, "systemCpu" }; +const StaticQName QN_CALLPERF_DATAPOINT_CPUS = { STR_EMPTY, "cpus" }; +const StaticQName QN_CALLPERF_CONNECTION = + { NS_GOOGLE_CALLPERF_STATS, "connection" }; +const StaticQName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS = + { STR_EMPTY, "localAddress" }; +const StaticQName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS = + { STR_EMPTY, "remoteAddress" }; +const StaticQName QN_CALLPERF_CONNECTION_FLAGS = { STR_EMPTY, "flags" }; +const StaticQName QN_CALLPERF_CONNECTION_RTT = { STR_EMPTY, "rtt" }; +const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S = + { STR_EMPTY, "totalBytesSent" }; +const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_S = + { STR_EMPTY, "bytesSecondSent" }; +const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R = + { STR_EMPTY, "totalBytesRecv" }; +const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_R = + { STR_EMPTY, "bytesSecondRecv" }; +const StaticQName QN_CALLPERF_CANDIDATE = + { NS_GOOGLE_CALLPERF_STATS, "candidate" }; +const StaticQName QN_CALLPERF_CANDIDATE_ENDPOINT = { STR_EMPTY, "endpoint" }; +const StaticQName QN_CALLPERF_CANDIDATE_PROTOCOL = { STR_EMPTY, "protocol" }; +const StaticQName QN_CALLPERF_CANDIDATE_ADDRESS = { STR_EMPTY, "address" }; +const StaticQName QN_CALLPERF_MEDIA = { NS_GOOGLE_CALLPERF_STATS, "media" }; +const StaticQName QN_CALLPERF_MEDIA_DIRECTION = { STR_EMPTY, "direction" }; +const StaticQName QN_CALLPERF_MEDIA_SSRC = { STR_EMPTY, "SSRC" }; +const StaticQName QN_CALLPERF_MEDIA_ENERGY = { STR_EMPTY, "energy" }; +const StaticQName QN_CALLPERF_MEDIA_FIR = { STR_EMPTY, "fir" }; +const StaticQName QN_CALLPERF_MEDIA_NACK = { STR_EMPTY, "nack" }; +const StaticQName QN_CALLPERF_MEDIA_FPS = { STR_EMPTY, "fps" }; +const StaticQName QN_CALLPERF_MEDIA_FPS_NETWORK = { STR_EMPTY, "fpsNetwork" }; +const StaticQName QN_CALLPERF_MEDIA_FPS_DECODED = { STR_EMPTY, "fpsDecoded" }; +const StaticQName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE = + { STR_EMPTY, "jitterBufferSize" }; +const StaticQName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE = + { STR_EMPTY, "preferredJitterBufferSize" }; +const StaticQName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY = + { STR_EMPTY, "totalPlayoutDelay" }; + +// Muc invites. +const StaticQName QN_MUC_USER_INVITE = { NS_MUC_USER, "invite" }; + +// Multiway audio/video. +const char NS_GOOGLE_MUC_USER[] = "google:muc#user"; +const StaticQName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA = + { NS_GOOGLE_MUC_USER, "available-media" }; +const StaticQName QN_GOOGLE_MUC_USER_ENTRY = { NS_GOOGLE_MUC_USER, "entry" }; +const StaticQName QN_GOOGLE_MUC_USER_MEDIA = { NS_GOOGLE_MUC_USER, "media" }; +const StaticQName QN_GOOGLE_MUC_USER_TYPE = { NS_GOOGLE_MUC_USER, "type" }; +const StaticQName QN_GOOGLE_MUC_USER_SRC_ID = { NS_GOOGLE_MUC_USER, "src-id" }; +const StaticQName QN_GOOGLE_MUC_USER_STATUS = { NS_GOOGLE_MUC_USER, "status" }; +const StaticQName QN_CLIENT_VERSION = { NS_GOOGLE_MUC_USER, "client-version" }; +const StaticQName QN_LOCALE = { NS_GOOGLE_MUC_USER, "locale" }; +const StaticQName QN_LABEL = { STR_EMPTY, "label" }; + +const char NS_GOOGLE_MUC_MEDIA[] = "google:muc#media"; +const StaticQName QN_GOOGLE_MUC_AUDIO_MUTE = + { NS_GOOGLE_MUC_MEDIA, "audio-mute" }; +const StaticQName QN_GOOGLE_MUC_VIDEO_MUTE = + { NS_GOOGLE_MUC_MEDIA, "video-mute" }; +const StaticQName QN_GOOGLE_MUC_VIDEO_PAUSE = + { NS_GOOGLE_MUC_MEDIA, "video-pause" }; +const StaticQName QN_GOOGLE_MUC_RECORDING = + { NS_GOOGLE_MUC_MEDIA, "recording" }; +const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK = { NS_GOOGLE_MUC_MEDIA, "block" }; +const StaticQName QN_STATE_ATTR = { STR_EMPTY, "state" }; + +const char AUTH_MECHANISM_GOOGLE_COOKIE[] = "X-GOOGLE-COOKIE"; +const char AUTH_MECHANISM_GOOGLE_TOKEN[] = "X-GOOGLE-TOKEN"; +const char AUTH_MECHANISM_OAUTH2[] = "X-OAUTH2"; +const char AUTH_MECHANISM_PLAIN[] = "PLAIN"; + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/constants.h b/webrtc/libjingle/xmpp/constants.h new file mode 100644 index 0000000000..5c1967e5f7 --- /dev/null +++ b/webrtc/libjingle/xmpp/constants.h @@ -0,0 +1,551 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_CONSTANTS_H_ +#define WEBRTC_LIBJINGLE_XMPP_CONSTANTS_H_ + +#include +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmpp/jid.h" + +namespace buzz { + +extern const char NS_CLIENT[]; +extern const char NS_SERVER[]; +extern const char NS_STREAM[]; +extern const char NS_XSTREAM[]; +extern const char NS_TLS[]; +extern const char NS_SASL[]; +extern const char NS_BIND[]; +extern const char NS_DIALBACK[]; +extern const char NS_SESSION[]; +extern const char NS_STANZA[]; +extern const char NS_PRIVACY[]; +extern const char NS_ROSTER[]; +extern const char NS_VCARD[]; +extern const char NS_AVATAR_HASH[]; +extern const char NS_VCARD_UPDATE[]; +extern const char STR_CLIENT[]; +extern const char STR_SERVER[]; +extern const char STR_STREAM[]; + +extern const char STR_GET[]; +extern const char STR_SET[]; +extern const char STR_RESULT[]; +extern const char STR_ERROR[]; + +extern const char STR_FORM[]; +extern const char STR_SUBMIT[]; +extern const char STR_TEXT_SINGLE[]; +extern const char STR_LIST_SINGLE[]; +extern const char STR_LIST_MULTI[]; +extern const char STR_HIDDEN[]; +extern const char STR_FORM_TYPE[]; + +extern const char STR_FROM[]; +extern const char STR_TO[]; +extern const char STR_BOTH[]; +extern const char STR_REMOVE[]; +extern const char STR_TRUE[]; + +extern const char STR_TYPE[]; +extern const char STR_NAME[]; +extern const char STR_ID[]; +extern const char STR_JID[]; +extern const char STR_SUBSCRIPTION[]; +extern const char STR_ASK[]; +extern const char STR_X[]; +extern const char STR_GOOGLE_COM[]; +extern const char STR_GMAIL_COM[]; +extern const char STR_GOOGLEMAIL_COM[]; +extern const char STR_DEFAULT_DOMAIN[]; +extern const char STR_TALK_GOOGLE_COM[]; +extern const char STR_TALKX_L_GOOGLE_COM[]; +extern const char STR_XMPP_GOOGLE_COM[]; +extern const char STR_XMPPX_L_GOOGLE_COM[]; + +#ifdef FEATURE_ENABLE_VOICEMAIL +extern const char STR_VOICEMAIL[]; +extern const char STR_OUTGOINGVOICEMAIL[]; +#endif + +extern const char STR_UNAVAILABLE[]; + +extern const char NS_PING[]; +extern const StaticQName QN_PING; + +extern const char NS_MUC_UNIQUE[]; +extern const StaticQName QN_MUC_UNIQUE_QUERY; +extern const StaticQName QN_HANGOUT_ID; + +extern const char STR_GOOGLE_MUC_LOOKUP_JID[]; +extern const char STR_MUC_ROOMCONFIG_ROOMNAME[]; +extern const char STR_MUC_ROOMCONFIG_FEATURES[]; +extern const char STR_MUC_ROOM_FEATURE_ENTERPRISE[]; +extern const char STR_MUC_ROOMCONFIG[]; +extern const char STR_MUC_ROOM_FEATURE_HANGOUT[]; +extern const char STR_MUC_ROOM_FEATURE_HANGOUT_LITE[]; +extern const char STR_MUC_ROOM_FEATURE_BROADCAST[]; +extern const char STR_MUC_ROOM_FEATURE_MULTI_USER_VC[]; +extern const char STR_MUC_ROOM_FEATURE_RECORDABLE[]; +extern const char STR_MUC_ROOM_FEATURE_CUSTOM_RECORDING[]; +extern const char STR_MUC_ROOM_OWNER_PROFILE_ID[]; +extern const char STR_MUC_ROOM_FEATURE_ABUSE_RECORDABLE[]; + +extern const char STR_ID_TYPE_CONVERSATION[]; +extern const char NS_GOOGLE_MUC_HANGOUT[]; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE_TYPE; +extern const StaticQName QN_ATTR_CREATE_ACTIVITY; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_PUBLIC; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITEE; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_STATUS; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_TYPE; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_HANGOUT_START_CONTEXT; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_CONVERSATION_ID; + +extern const StaticQName QN_STREAM_STREAM; +extern const StaticQName QN_STREAM_FEATURES; +extern const StaticQName QN_STREAM_ERROR; + +extern const StaticQName QN_XSTREAM_BAD_FORMAT; +extern const StaticQName QN_XSTREAM_BAD_NAMESPACE_PREFIX; +extern const StaticQName QN_XSTREAM_CONFLICT; +extern const StaticQName QN_XSTREAM_CONNECTION_TIMEOUT; +extern const StaticQName QN_XSTREAM_HOST_GONE; +extern const StaticQName QN_XSTREAM_HOST_UNKNOWN; +extern const StaticQName QN_XSTREAM_IMPROPER_ADDRESSIING; +extern const StaticQName QN_XSTREAM_INTERNAL_SERVER_ERROR; +extern const StaticQName QN_XSTREAM_INVALID_FROM; +extern const StaticQName QN_XSTREAM_INVALID_ID; +extern const StaticQName QN_XSTREAM_INVALID_NAMESPACE; +extern const StaticQName QN_XSTREAM_INVALID_XML; +extern const StaticQName QN_XSTREAM_NOT_AUTHORIZED; +extern const StaticQName QN_XSTREAM_POLICY_VIOLATION; +extern const StaticQName QN_XSTREAM_REMOTE_CONNECTION_FAILED; +extern const StaticQName QN_XSTREAM_RESOURCE_CONSTRAINT; +extern const StaticQName QN_XSTREAM_RESTRICTED_XML; +extern const StaticQName QN_XSTREAM_SEE_OTHER_HOST; +extern const StaticQName QN_XSTREAM_SYSTEM_SHUTDOWN; +extern const StaticQName QN_XSTREAM_UNDEFINED_CONDITION; +extern const StaticQName QN_XSTREAM_UNSUPPORTED_ENCODING; +extern const StaticQName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE; +extern const StaticQName QN_XSTREAM_UNSUPPORTED_VERSION; +extern const StaticQName QN_XSTREAM_XML_NOT_WELL_FORMED; +extern const StaticQName QN_XSTREAM_TEXT; + +extern const StaticQName QN_TLS_STARTTLS; +extern const StaticQName QN_TLS_REQUIRED; +extern const StaticQName QN_TLS_PROCEED; +extern const StaticQName QN_TLS_FAILURE; + +extern const StaticQName QN_SASL_MECHANISMS; +extern const StaticQName QN_SASL_MECHANISM; +extern const StaticQName QN_SASL_AUTH; +extern const StaticQName QN_SASL_CHALLENGE; +extern const StaticQName QN_SASL_RESPONSE; +extern const StaticQName QN_SASL_ABORT; +extern const StaticQName QN_SASL_SUCCESS; +extern const StaticQName QN_SASL_FAILURE; +extern const StaticQName QN_SASL_ABORTED; +extern const StaticQName QN_SASL_INCORRECT_ENCODING; +extern const StaticQName QN_SASL_INVALID_AUTHZID; +extern const StaticQName QN_SASL_INVALID_MECHANISM; +extern const StaticQName QN_SASL_MECHANISM_TOO_WEAK; +extern const StaticQName QN_SASL_NOT_AUTHORIZED; +extern const StaticQName QN_SASL_TEMPORARY_AUTH_FAILURE; + +// These are non-standard. +extern const char NS_GOOGLE_AUTH[]; +extern const char NS_GOOGLE_AUTH_PROTOCOL[]; +extern const StaticQName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT; +extern const StaticQName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN; +extern const StaticQName QN_GOOGLE_AUTH_SERVICE; + +extern const StaticQName QN_DIALBACK_RESULT; +extern const StaticQName QN_DIALBACK_VERIFY; + +extern const StaticQName QN_STANZA_BAD_REQUEST; +extern const StaticQName QN_STANZA_CONFLICT; +extern const StaticQName QN_STANZA_FEATURE_NOT_IMPLEMENTED; +extern const StaticQName QN_STANZA_FORBIDDEN; +extern const StaticQName QN_STANZA_GONE; +extern const StaticQName QN_STANZA_INTERNAL_SERVER_ERROR; +extern const StaticQName QN_STANZA_ITEM_NOT_FOUND; +extern const StaticQName QN_STANZA_JID_MALFORMED; +extern const StaticQName QN_STANZA_NOT_ACCEPTABLE; +extern const StaticQName QN_STANZA_NOT_ALLOWED; +extern const StaticQName QN_STANZA_PAYMENT_REQUIRED; +extern const StaticQName QN_STANZA_RECIPIENT_UNAVAILABLE; +extern const StaticQName QN_STANZA_REDIRECT; +extern const StaticQName QN_STANZA_REGISTRATION_REQUIRED; +extern const StaticQName QN_STANZA_REMOTE_SERVER_NOT_FOUND; +extern const StaticQName QN_STANZA_REMOTE_SERVER_TIMEOUT; +extern const StaticQName QN_STANZA_RESOURCE_CONSTRAINT; +extern const StaticQName QN_STANZA_SERVICE_UNAVAILABLE; +extern const StaticQName QN_STANZA_SUBSCRIPTION_REQUIRED; +extern const StaticQName QN_STANZA_UNDEFINED_CONDITION; +extern const StaticQName QN_STANZA_UNEXPECTED_REQUEST; +extern const StaticQName QN_STANZA_TEXT; + +extern const StaticQName QN_BIND_BIND; +extern const StaticQName QN_BIND_RESOURCE; +extern const StaticQName QN_BIND_JID; + +extern const StaticQName QN_MESSAGE; +extern const StaticQName QN_BODY; +extern const StaticQName QN_SUBJECT; +extern const StaticQName QN_THREAD; +extern const StaticQName QN_PRESENCE; +extern const StaticQName QN_SHOW; +extern const StaticQName QN_STATUS; +extern const StaticQName QN_LANG; +extern const StaticQName QN_PRIORITY; +extern const StaticQName QN_IQ; +extern const StaticQName QN_ERROR; + +extern const StaticQName QN_SERVER_MESSAGE; +extern const StaticQName QN_SERVER_BODY; +extern const StaticQName QN_SERVER_SUBJECT; +extern const StaticQName QN_SERVER_THREAD; +extern const StaticQName QN_SERVER_PRESENCE; +extern const StaticQName QN_SERVER_SHOW; +extern const StaticQName QN_SERVER_STATUS; +extern const StaticQName QN_SERVER_LANG; +extern const StaticQName QN_SERVER_PRIORITY; +extern const StaticQName QN_SERVER_IQ; +extern const StaticQName QN_SERVER_ERROR; + +extern const StaticQName QN_SESSION_SESSION; + +extern const StaticQName QN_PRIVACY_QUERY; +extern const StaticQName QN_PRIVACY_ACTIVE; +extern const StaticQName QN_PRIVACY_DEFAULT; +extern const StaticQName QN_PRIVACY_LIST; +extern const StaticQName QN_PRIVACY_ITEM; +extern const StaticQName QN_PRIVACY_IQ; +extern const StaticQName QN_PRIVACY_MESSAGE; +extern const StaticQName QN_PRIVACY_PRESENCE_IN; +extern const StaticQName QN_PRIVACY_PRESENCE_OUT; + +extern const StaticQName QN_ROSTER_QUERY; +extern const StaticQName QN_ROSTER_ITEM; +extern const StaticQName QN_ROSTER_GROUP; + +extern const StaticQName QN_VCARD; +extern const StaticQName QN_VCARD_FN; +extern const StaticQName QN_VCARD_PHOTO; +extern const StaticQName QN_VCARD_PHOTO_BINVAL; +extern const StaticQName QN_VCARD_AVATAR_HASH; +extern const StaticQName QN_VCARD_AVATAR_HASH_MODIFIED; + +#if defined(FEATURE_ENABLE_PSTN) +extern const StaticQName QN_VCARD_TEL; +extern const StaticQName QN_VCARD_VOICE; +extern const StaticQName QN_VCARD_HOME; +extern const StaticQName QN_VCARD_WORK; +extern const StaticQName QN_VCARD_CELL; +extern const StaticQName QN_VCARD_NUMBER; +#endif + +#if defined(FEATURE_ENABLE_RICHPROFILES) +extern const StaticQName QN_USER_PROFILE_QUERY; +extern const StaticQName QN_USER_PROFILE_URL; + +extern const StaticQName QN_ATOM_FEED; +extern const StaticQName QN_ATOM_ENTRY; +extern const StaticQName QN_ATOM_TITLE; +extern const StaticQName QN_ATOM_ID; +extern const StaticQName QN_ATOM_MODIFIED; +extern const StaticQName QN_ATOM_IMAGE; +extern const StaticQName QN_ATOM_LINK; +extern const StaticQName QN_ATOM_HREF; +#endif + +extern const StaticQName QN_XML_LANG; + +extern const StaticQName QN_ENCODING; +extern const StaticQName QN_VERSION; +extern const StaticQName QN_TO; +extern const StaticQName QN_FROM; +extern const StaticQName QN_TYPE; +extern const StaticQName QN_ID; +extern const StaticQName QN_CODE; +extern const StaticQName QN_NAME; +extern const StaticQName QN_VALUE; +extern const StaticQName QN_ACTION; +extern const StaticQName QN_ORDER; +extern const StaticQName QN_MECHANISM; +extern const StaticQName QN_ASK; +extern const StaticQName QN_JID; +extern const StaticQName QN_NICK; +extern const StaticQName QN_SUBSCRIPTION; +extern const StaticQName QN_TITLE1; +extern const StaticQName QN_TITLE2; +extern const StaticQName QN_AFFILIATION; +extern const StaticQName QN_ROLE; +extern const StaticQName QN_TIME; + +extern const StaticQName QN_XMLNS_CLIENT; +extern const StaticQName QN_XMLNS_SERVER; +extern const StaticQName QN_XMLNS_STREAM; + +// Presence +extern const char STR_SHOW_AWAY[]; +extern const char STR_SHOW_CHAT[]; +extern const char STR_SHOW_DND[]; +extern const char STR_SHOW_XA[]; +extern const char STR_SHOW_OFFLINE[]; + +extern const char NS_GOOGLE_PSTN_CONFERENCE[]; +extern const StaticQName QN_GOOGLE_PSTN_CONFERENCE_STATUS; +extern const StaticQName QN_ATTR_STATUS; + +// Presence connection status +extern const char STR_PSTN_CONFERENCE_STATUS_CONNECTING[]; +extern const char STR_PSTN_CONFERENCE_STATUS_JOINING[]; +extern const char STR_PSTN_CONFERENCE_STATUS_CONNECTED[]; +extern const char STR_PSTN_CONFERENCE_STATUS_HANGUP[]; + +// Subscription +extern const char STR_SUBSCRIBE[]; +extern const char STR_SUBSCRIBED[]; +extern const char STR_UNSUBSCRIBE[]; +extern const char STR_UNSUBSCRIBED[]; + +// Google Invite +extern const char NS_GOOGLE_SUBSCRIBE[]; +extern const StaticQName QN_INVITATION; +extern const StaticQName QN_INVITE_NAME; +extern const StaticQName QN_INVITE_SUBJECT; +extern const StaticQName QN_INVITE_MESSAGE; + +// Kick +extern const char NS_GOOGLE_MUC_ADMIN[]; +extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY; +extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM; +extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM_REASON; + +// PubSub: http://xmpp.org/extensions/xep-0060.html +extern const char NS_PUBSUB[]; +extern const StaticQName QN_PUBSUB; +extern const StaticQName QN_PUBSUB_ITEMS; +extern const StaticQName QN_PUBSUB_ITEM; +extern const StaticQName QN_PUBSUB_PUBLISH; +extern const StaticQName QN_PUBSUB_RETRACT; +extern const StaticQName QN_ATTR_PUBLISHER; + +extern const char NS_PUBSUB_EVENT[]; +extern const StaticQName QN_NODE; +extern const StaticQName QN_PUBSUB_EVENT; +extern const StaticQName QN_PUBSUB_EVENT_ITEMS; +extern const StaticQName QN_PUBSUB_EVENT_ITEM; +extern const StaticQName QN_PUBSUB_EVENT_RETRACT; +extern const StaticQName QN_NOTIFY; + +extern const char NS_PRESENTER[]; +extern const StaticQName QN_PRESENTER_PRESENTER; +extern const StaticQName QN_PRESENTER_PRESENTATION_ITEM; +extern const StaticQName QN_PRESENTER_PRESENTATION_TYPE; +extern const StaticQName QN_PRESENTER_PRESENTATION_ID; + +// JEP 0030 +extern const StaticQName QN_CATEGORY; +extern const StaticQName QN_VAR; +extern const char NS_DISCO_INFO[]; +extern const char NS_DISCO_ITEMS[]; + +extern const StaticQName QN_DISCO_INFO_QUERY; +extern const StaticQName QN_DISCO_IDENTITY; +extern const StaticQName QN_DISCO_FEATURE; + +extern const StaticQName QN_DISCO_ITEMS_QUERY; +extern const StaticQName QN_DISCO_ITEM; + +// JEP 0020 +extern const char NS_FEATURE[]; +extern const StaticQName QN_FEATURE_FEATURE; + +// JEP 0004 +extern const char NS_XDATA[]; +extern const StaticQName QN_XDATA_X; +extern const StaticQName QN_XDATA_INSTRUCTIONS; +extern const StaticQName QN_XDATA_TITLE; +extern const StaticQName QN_XDATA_FIELD; +extern const StaticQName QN_XDATA_REPORTED; +extern const StaticQName QN_XDATA_ITEM; +extern const StaticQName QN_XDATA_DESC; +extern const StaticQName QN_XDATA_REQUIRED; +extern const StaticQName QN_XDATA_VALUE; +extern const StaticQName QN_XDATA_OPTION; + +// JEP 0045 +extern const char NS_MUC[]; +extern const StaticQName QN_MUC_X; +extern const StaticQName QN_MUC_ITEM; +extern const StaticQName QN_MUC_AFFILIATION; +extern const StaticQName QN_MUC_ROLE; +extern const StaticQName QN_CLIENT_VERSION; +extern const StaticQName QN_LOCALE; +extern const char STR_AFFILIATION_NONE[]; +extern const char STR_ROLE_PARTICIPANT[]; + +extern const char NS_GOOGLE_SESSION[]; +extern const StaticQName QN_GOOGLE_USER_ID; +extern const StaticQName QN_GOOGLE_CIRCLE_ID; +extern const StaticQName QN_GOOGLE_SESSION_BLOCKED; +extern const StaticQName QN_GOOGLE_SESSION_BLOCKING; + +extern const char NS_MUC_OWNER[]; +extern const StaticQName QN_MUC_OWNER_QUERY; + +extern const char NS_MUC_USER[]; +extern const StaticQName QN_MUC_USER_CONTINUE; +extern const StaticQName QN_MUC_USER_X; +extern const StaticQName QN_MUC_USER_ITEM; +extern const StaticQName QN_MUC_USER_STATUS; +extern const StaticQName QN_MUC_USER_REASON; +extern const StaticQName QN_MUC_USER_ABUSE_VIOLATION; + +// JEP 0055 - Jabber Search +extern const char NS_SEARCH[]; +extern const StaticQName QN_SEARCH_QUERY; +extern const StaticQName QN_SEARCH_ITEM; +extern const StaticQName QN_SEARCH_ROOM_NAME; +extern const StaticQName QN_SEARCH_ROOM_JID; +extern const StaticQName QN_SEARCH_ROOM_DOMAIN; +extern const StaticQName QN_SEARCH_HANGOUT_ID; +extern const StaticQName QN_SEARCH_EXTERNAL_ID; + +// JEP 0115 +extern const char NS_CAPS[]; +extern const StaticQName QN_CAPS_C; +extern const StaticQName QN_VER; +extern const StaticQName QN_EXT; + + +// Avatar - JEP 0153 +extern const char kNSVCard[]; +extern const StaticQName kQnVCardX; +extern const StaticQName kQnVCardPhoto; + +// JEP 0172 User Nickname +extern const char NS_NICKNAME[]; +extern const StaticQName QN_NICKNAME; + +// JEP 0085 chat state +extern const char NS_CHATSTATE[]; +extern const StaticQName QN_CS_ACTIVE; +extern const StaticQName QN_CS_COMPOSING; +extern const StaticQName QN_CS_PAUSED; +extern const StaticQName QN_CS_INACTIVE; +extern const StaticQName QN_CS_GONE; + +// JEP 0091 Delayed Delivery +extern const char kNSDelay[]; +extern const StaticQName kQnDelayX; +extern const StaticQName kQnStamp; + +// Google time stamping (higher resolution) +extern const char kNSTimestamp[]; +extern const StaticQName kQnTime; +extern const StaticQName kQnMilliseconds; + +extern const char NS_JINGLE_INFO[]; +extern const StaticQName QN_JINGLE_INFO_QUERY; +extern const StaticQName QN_JINGLE_INFO_STUN; +extern const StaticQName QN_JINGLE_INFO_RELAY; +extern const StaticQName QN_JINGLE_INFO_SERVER; +extern const StaticQName QN_JINGLE_INFO_TOKEN; +extern const StaticQName QN_JINGLE_INFO_HOST; +extern const StaticQName QN_JINGLE_INFO_TCP; +extern const StaticQName QN_JINGLE_INFO_UDP; +extern const StaticQName QN_JINGLE_INFO_TCPSSL; + +extern const char NS_GOOGLE_CALLPERF_STATS[]; +extern const StaticQName QN_CALLPERF_STATS; +extern const StaticQName QN_CALLPERF_SESSIONID; +extern const StaticQName QN_CALLPERF_LOCALUSER; +extern const StaticQName QN_CALLPERF_REMOTEUSER; +extern const StaticQName QN_CALLPERF_STARTTIME; +extern const StaticQName QN_CALLPERF_CALL_LENGTH; +extern const StaticQName QN_CALLPERF_CALL_ACCEPTED; +extern const StaticQName QN_CALLPERF_CALL_ERROR_CODE; +extern const StaticQName QN_CALLPERF_TERMINATE_CODE; +extern const StaticQName QN_CALLPERF_DATAPOINT; +extern const StaticQName QN_CALLPERF_DATAPOINT_TIME; +extern const StaticQName QN_CALLPERF_DATAPOINT_FRACTION_LOST; +extern const StaticQName QN_CALLPERF_DATAPOINT_CUM_LOST; +extern const StaticQName QN_CALLPERF_DATAPOINT_EXT_MAX; +extern const StaticQName QN_CALLPERF_DATAPOINT_JITTER; +extern const StaticQName QN_CALLPERF_DATAPOINT_RTT; +extern const StaticQName QN_CALLPERF_DATAPOINT_BYTES_R; +extern const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_R; +extern const StaticQName QN_CALLPERF_DATAPOINT_BYTES_S; +extern const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_S; +extern const StaticQName QN_CALLPERF_DATAPOINT_PROCESS_CPU; +extern const StaticQName QN_CALLPERF_DATAPOINT_SYSTEM_CPU; +extern const StaticQName QN_CALLPERF_DATAPOINT_CPUS; +extern const StaticQName QN_CALLPERF_CONNECTION; +extern const StaticQName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS; +extern const StaticQName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS; +extern const StaticQName QN_CALLPERF_CONNECTION_FLAGS; +extern const StaticQName QN_CALLPERF_CONNECTION_RTT; +extern const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S; +extern const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_S; +extern const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R; +extern const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_R; +extern const StaticQName QN_CALLPERF_CANDIDATE; +extern const StaticQName QN_CALLPERF_CANDIDATE_ENDPOINT; +extern const StaticQName QN_CALLPERF_CANDIDATE_PROTOCOL; +extern const StaticQName QN_CALLPERF_CANDIDATE_ADDRESS; +extern const StaticQName QN_CALLPERF_MEDIA; +extern const StaticQName QN_CALLPERF_MEDIA_DIRECTION; +extern const StaticQName QN_CALLPERF_MEDIA_SSRC; +extern const StaticQName QN_CALLPERF_MEDIA_ENERGY; +extern const StaticQName QN_CALLPERF_MEDIA_FIR; +extern const StaticQName QN_CALLPERF_MEDIA_NACK; +extern const StaticQName QN_CALLPERF_MEDIA_FPS; +extern const StaticQName QN_CALLPERF_MEDIA_FPS_NETWORK; +extern const StaticQName QN_CALLPERF_MEDIA_FPS_DECODED; +extern const StaticQName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE; +extern const StaticQName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE; +extern const StaticQName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY; + +// Muc invites. +extern const StaticQName QN_MUC_USER_INVITE; + +// Multiway audio/video. +extern const char NS_GOOGLE_MUC_USER[]; +extern const StaticQName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA; +extern const StaticQName QN_GOOGLE_MUC_USER_ENTRY; +extern const StaticQName QN_GOOGLE_MUC_USER_MEDIA; +extern const StaticQName QN_GOOGLE_MUC_USER_TYPE; +extern const StaticQName QN_GOOGLE_MUC_USER_SRC_ID; +extern const StaticQName QN_GOOGLE_MUC_USER_STATUS; +extern const StaticQName QN_LABEL; + +extern const char NS_GOOGLE_MUC_MEDIA[]; +extern const StaticQName QN_GOOGLE_MUC_AUDIO_MUTE; +extern const StaticQName QN_GOOGLE_MUC_VIDEO_MUTE; +extern const StaticQName QN_GOOGLE_MUC_VIDEO_PAUSE; +extern const StaticQName QN_GOOGLE_MUC_RECORDING; +extern const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK; +extern const StaticQName QN_STATE_ATTR; + + +extern const char AUTH_MECHANISM_GOOGLE_COOKIE[]; +extern const char AUTH_MECHANISM_GOOGLE_TOKEN[]; +extern const char AUTH_MECHANISM_OAUTH2[]; +extern const char AUTH_MECHANISM_PLAIN[]; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_CONSTANTS_H_ diff --git a/webrtc/libjingle/xmpp/discoitemsquerytask.cc b/webrtc/libjingle/xmpp/discoitemsquerytask.cc new file mode 100644 index 0000000000..10f6e1d745 --- /dev/null +++ b/webrtc/libjingle/xmpp/discoitemsquerytask.cc @@ -0,0 +1,61 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/discoitemsquerytask.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +DiscoItemsQueryTask::DiscoItemsQueryTask(XmppTaskParentInterface* parent, + const Jid& to, + const std::string& node) + : IqTask(parent, STR_GET, to, MakeRequest(node)) { +} + +XmlElement* DiscoItemsQueryTask::MakeRequest(const std::string& node) { + XmlElement* element = new XmlElement(QN_DISCO_ITEMS_QUERY, true); + if (!node.empty()) { + element->AddAttr(QN_NODE, node); + } + return element; +} + +void DiscoItemsQueryTask::HandleResult(const XmlElement* stanza) { + const XmlElement* query = stanza->FirstNamed(QN_DISCO_ITEMS_QUERY); + if (query) { + std::vector items; + for (const buzz::XmlChild* child = query->FirstChild(); child; + child = child->NextChild()) { + DiscoItem item; + const buzz::XmlElement* child_element = child->AsElement(); + if (ParseItem(child_element, &item)) { + items.push_back(item); + } + } + SignalResult(items); + } else { + SignalError(this, NULL); + } +} + +bool DiscoItemsQueryTask::ParseItem(const XmlElement* element, + DiscoItem* item) { + if (element->HasAttr(QN_JID)) { + return false; + } + + item->jid = element->Attr(QN_JID); + item->name = element->Attr(QN_NAME); + item->node = element->Attr(QN_NODE); + return true; +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/discoitemsquerytask.h b/webrtc/libjingle/xmpp/discoitemsquerytask.h new file mode 100644 index 0000000000..62e862e193 --- /dev/null +++ b/webrtc/libjingle/xmpp/discoitemsquerytask.h @@ -0,0 +1,65 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Fires a disco items query, such as the following example: +// +// +// +// +// +// Sample response: +// +// +// +// +// +// + + +#ifndef WEBRTC_LIBJINGLE_XMPP_DISCOITEMSQUERYTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_DISCOITEMSQUERYTASK_H_ + +#include +#include + +#include "webrtc/libjingle/xmpp/iqtask.h" + +namespace buzz { + +struct DiscoItem { + std::string jid; + std::string node; + std::string name; +}; + +class DiscoItemsQueryTask : public IqTask { + public: + DiscoItemsQueryTask(XmppTaskParentInterface* parent, + const Jid& to, const std::string& node); + + sigslot::signal1 > SignalResult; + + private: + static XmlElement* MakeRequest(const std::string& node); + virtual void HandleResult(const XmlElement* result); + static bool ParseItem(const XmlElement* element, DiscoItem* item); +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_DISCOITEMSQUERYTASK_H_ diff --git a/webrtc/libjingle/xmpp/fakexmppclient.h b/webrtc/libjingle/xmpp/fakexmppclient.h new file mode 100644 index 0000000000..63c216caf2 --- /dev/null +++ b/webrtc/libjingle/xmpp/fakexmppclient.h @@ -0,0 +1,107 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// A fake XmppClient for use in unit tests. + +#ifndef WEBRTC_LIBJINGLE_XMPP_FAKEXMPPCLIENT_H_ +#define WEBRTC_LIBJINGLE_XMPP_FAKEXMPPCLIENT_H_ + +#include +#include +#include + +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +class XmlElement; + +class FakeXmppClient : public XmppTaskParentInterface, + public XmppClientInterface { + public: + explicit FakeXmppClient(rtc::TaskParent* parent) + : XmppTaskParentInterface(parent) { + } + + // As XmppTaskParentInterface + virtual XmppClientInterface* GetClient() { + return this; + } + + virtual int ProcessStart() { + return STATE_RESPONSE; + } + + // As XmppClientInterface + virtual XmppEngine::State GetState() const { + return XmppEngine::STATE_OPEN; + } + + virtual const Jid& jid() const { + return jid_; + } + + virtual std::string NextId() { + // Implement if needed for tests. + return "0"; + } + + virtual XmppReturnStatus SendStanza(const XmlElement* stanza) { + sent_stanzas_.push_back(stanza); + return XMPP_RETURN_OK; + } + + const std::vector& sent_stanzas() { + return sent_stanzas_; + } + + virtual XmppReturnStatus SendStanzaError( + const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text) { + // Implement if needed for tests. + return XMPP_RETURN_OK; + } + + virtual void AddXmppTask(XmppTask* task, + XmppEngine::HandlerLevel level) { + tasks_.push_back(task); + } + + virtual void RemoveXmppTask(XmppTask* task) { + std::remove(tasks_.begin(), tasks_.end(), task); + } + + // As FakeXmppClient + void set_jid(const Jid& jid) { + jid_ = jid; + } + + // Takes ownership of stanza. + void HandleStanza(XmlElement* stanza) { + for (std::vector::iterator task = tasks_.begin(); + task != tasks_.end(); ++task) { + if ((*task)->HandleStanza(stanza)) { + delete stanza; + return; + } + } + delete stanza; + } + + private: + Jid jid_; + std::vector tasks_; + std::vector sent_stanzas_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_FAKEXMPPCLIENT_H_ diff --git a/webrtc/libjingle/xmpp/hangoutpubsubclient.cc b/webrtc/libjingle/xmpp/hangoutpubsubclient.cc new file mode 100644 index 0000000000..db1ac31491 --- /dev/null +++ b/webrtc/libjingle/xmpp/hangoutpubsubclient.cc @@ -0,0 +1,400 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h" + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/base/logging.h" + + +// Gives a high-level API for MUC call PubSub needs such as +// presenter state, recording state, mute state, and remote mute. + +namespace buzz { + +namespace { +const char kPresenting[] = "s"; +const char kNotPresenting[] = "o"; + +} // namespace + +// A simple serialiazer where presence of item => true, lack of item +// => false. +class BoolStateSerializer : public PubSubStateSerializer { + virtual XmlElement* Write(const QName& state_name, const bool& state) { + if (!state) { + return NULL; + } + + return new XmlElement(state_name, true); + } + + virtual void Parse(const XmlElement* state_elem, bool *state_out) { + *state_out = state_elem != NULL; + } +}; + +class PresenterStateClient : public PubSubStateClient { + public: + PresenterStateClient(const std::string& publisher_nick, + PubSubClient* client, + const QName& state_name, + bool default_state) + : PubSubStateClient( + publisher_nick, client, state_name, default_state, + new PublishedNickKeySerializer(), NULL) { + } + + virtual void Publish(const std::string& published_nick, + const bool& state, + std::string* task_id_out) { + XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true); + presenter_elem->AddAttr(QN_NICK, published_nick); + + XmlElement* presentation_item_elem = + new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false); + const std::string& presentation_type = state ? kPresenting : kNotPresenting; + presentation_item_elem->AddAttr( + QN_PRESENTER_PRESENTATION_TYPE, presentation_type); + + // The Presenter state is kind of dumb in that it doesn't always use + // retracts. It relies on setting the "type" to a special value. + std::string itemid = published_nick; + std::vector children; + children.push_back(presenter_elem); + children.push_back(presentation_item_elem); + client()->PublishItem(itemid, children, task_id_out); + } + + protected: + virtual bool ParseStateItem(const PubSubItem& item, + StateItemInfo* info_out, + bool* state_out) { + const XmlElement* presenter_elem = + item.elem->FirstNamed(QN_PRESENTER_PRESENTER); + const XmlElement* presentation_item_elem = + item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM); + if (presentation_item_elem == NULL || presenter_elem == NULL) { + return false; + } + + info_out->publisher_nick = + client()->GetPublisherNickFromPubSubItem(item.elem); + info_out->published_nick = presenter_elem->Attr(QN_NICK); + *state_out = (presentation_item_elem->Attr( + QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting); + return true; + } + + virtual bool StatesEqual(const bool& state1, const bool& state2) { + // Make every item trigger an event, even if state doesn't change. + return false; + } +}; + +HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent, + const Jid& mucjid, + const std::string& nick) + : mucjid_(mucjid), + nick_(nick) { + presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER)); + presenter_client_->SignalRequestError.connect( + this, &HangoutPubSubClient::OnPresenterRequestError); + + media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA)); + media_client_->SignalRequestError.connect( + this, &HangoutPubSubClient::OnMediaRequestError); + + presenter_state_client_.reset(new PresenterStateClient( + nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false)); + presenter_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnPresenterStateChange); + presenter_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnPresenterPublishResult); + presenter_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnPresenterPublishError); + + audio_mute_state_client_.reset(new PubSubStateClient( + nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false, + new PublishedNickKeySerializer(), new BoolStateSerializer())); + // Can't just repeat because we need to watch for remote mutes. + audio_mute_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnAudioMuteStateChange); + audio_mute_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnAudioMutePublishResult); + audio_mute_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnAudioMutePublishError); + + video_mute_state_client_.reset(new PubSubStateClient( + nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_MUTE, false, + new PublishedNickKeySerializer(), new BoolStateSerializer())); + // Can't just repeat because we need to watch for remote mutes. + video_mute_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnVideoMuteStateChange); + video_mute_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnVideoMutePublishResult); + video_mute_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnVideoMutePublishError); + + video_pause_state_client_.reset(new PubSubStateClient( + nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_PAUSE, false, + new PublishedNickKeySerializer(), new BoolStateSerializer())); + video_pause_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnVideoPauseStateChange); + video_pause_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnVideoPausePublishResult); + video_pause_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnVideoPausePublishError); + + recording_state_client_.reset(new PubSubStateClient( + nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false, + new PublishedNickKeySerializer(), new BoolStateSerializer())); + recording_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnRecordingStateChange); + recording_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnRecordingPublishResult); + recording_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnRecordingPublishError); + + media_block_state_client_.reset(new PubSubStateClient( + nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false, + new PublisherAndPublishedNicksKeySerializer(), + new BoolStateSerializer())); + media_block_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnMediaBlockStateChange); + media_block_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnMediaBlockPublishResult); + media_block_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnMediaBlockPublishError); +} + +HangoutPubSubClient::~HangoutPubSubClient() { +} + +void HangoutPubSubClient::RequestAll() { + presenter_client_->RequestItems(); + media_client_->RequestItems(); +} + +void HangoutPubSubClient::OnPresenterRequestError( + PubSubClient* client, const XmlElement* stanza) { + SignalRequestError(client->node(), stanza); +} + +void HangoutPubSubClient::OnMediaRequestError( + PubSubClient* client, const XmlElement* stanza) { + SignalRequestError(client->node(), stanza); +} + +void HangoutPubSubClient::PublishPresenterState( + bool presenting, std::string* task_id_out) { + presenter_state_client_->Publish(nick_, presenting, task_id_out); +} + +void HangoutPubSubClient::PublishAudioMuteState( + bool muted, std::string* task_id_out) { + audio_mute_state_client_->Publish(nick_, muted, task_id_out); +} + +void HangoutPubSubClient::PublishVideoMuteState( + bool muted, std::string* task_id_out) { + video_mute_state_client_->Publish(nick_, muted, task_id_out); +} + +void HangoutPubSubClient::PublishVideoPauseState( + bool paused, std::string* task_id_out) { + video_pause_state_client_->Publish(nick_, paused, task_id_out); +} + +void HangoutPubSubClient::PublishRecordingState( + bool recording, std::string* task_id_out) { + recording_state_client_->Publish(nick_, recording, task_id_out); +} + +// Remote mute is accomplished by setting another client's mute state. +void HangoutPubSubClient::RemoteMute( + const std::string& mutee_nick, std::string* task_id_out) { + audio_mute_state_client_->Publish(mutee_nick, true, task_id_out); +} + +// Block media is accomplished by setting another client's block +// state, kind of like remote mute. +void HangoutPubSubClient::BlockMedia( + const std::string& blockee_nick, std::string* task_id_out) { + media_block_state_client_->Publish(blockee_nick, true, task_id_out); +} + +void HangoutPubSubClient::OnPresenterStateChange( + const PubSubStateChange& change) { + SignalPresenterStateChange( + change.published_nick, change.old_state, change.new_state); +} + +void HangoutPubSubClient::OnPresenterPublishResult( + const std::string& task_id, const XmlElement* item) { + SignalPublishPresenterResult(task_id); +} + +void HangoutPubSubClient::OnPresenterPublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + SignalPublishPresenterError(task_id, stanza); +} + +// Since a remote mute is accomplished by another client setting our +// mute state, if our state changes to muted, we should mute ourselves. +// Note that remote un-muting is disallowed by the RoomServer. +void HangoutPubSubClient::OnAudioMuteStateChange( + const PubSubStateChange& change) { + bool was_muted = change.old_state; + bool is_muted = change.new_state; + bool remote_action = (!change.publisher_nick.empty() && + (change.publisher_nick != change.published_nick)); + + if (remote_action) { + const std::string& mutee_nick = change.published_nick; + const std::string& muter_nick = change.publisher_nick; + if (!is_muted) { + // The server should prevent remote un-mute. + LOG(LS_WARNING) << muter_nick << " remote unmuted " << mutee_nick; + return; + } + bool should_mute_locally = (mutee_nick == nick_); + SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally); + } + SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted); +} + +const std::string GetAudioMuteNickFromItem(const XmlElement* item) { + if (item != NULL) { + const XmlElement* audio_mute_state = + item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE); + if (audio_mute_state != NULL) { + return audio_mute_state->Attr(QN_NICK); + } + } + return std::string(); +} + +const std::string GetBlockeeNickFromItem(const XmlElement* item) { + if (item != NULL) { + const XmlElement* media_block_state = + item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK); + if (media_block_state != NULL) { + return media_block_state->Attr(QN_NICK); + } + } + return std::string(); +} + +void HangoutPubSubClient::OnAudioMutePublishResult( + const std::string& task_id, const XmlElement* item) { + const std::string& mutee_nick = GetAudioMuteNickFromItem(item); + if (mutee_nick != nick_) { + SignalRemoteMuteResult(task_id, mutee_nick); + } else { + SignalPublishAudioMuteResult(task_id); + } +} + +void HangoutPubSubClient::OnAudioMutePublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + const std::string& mutee_nick = GetAudioMuteNickFromItem(item); + if (mutee_nick != nick_) { + SignalRemoteMuteError(task_id, mutee_nick, stanza); + } else { + SignalPublishAudioMuteError(task_id, stanza); + } +} + +void HangoutPubSubClient::OnVideoMuteStateChange( + const PubSubStateChange& change) { + SignalVideoMuteStateChange( + change.published_nick, change.old_state, change.new_state); +} + +void HangoutPubSubClient::OnVideoMutePublishResult( + const std::string& task_id, const XmlElement* item) { + SignalPublishVideoMuteResult(task_id); +} + +void HangoutPubSubClient::OnVideoMutePublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + SignalPublishVideoMuteError(task_id, stanza); +} + +void HangoutPubSubClient::OnVideoPauseStateChange( + const PubSubStateChange& change) { + SignalVideoPauseStateChange( + change.published_nick, change.old_state, change.new_state); +} + +void HangoutPubSubClient::OnVideoPausePublishResult( + const std::string& task_id, const XmlElement* item) { + SignalPublishVideoPauseResult(task_id); +} + +void HangoutPubSubClient::OnVideoPausePublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + SignalPublishVideoPauseError(task_id, stanza); +} + +void HangoutPubSubClient::OnRecordingStateChange( + const PubSubStateChange& change) { + SignalRecordingStateChange( + change.published_nick, change.old_state, change.new_state); +} + +void HangoutPubSubClient::OnRecordingPublishResult( + const std::string& task_id, const XmlElement* item) { + SignalPublishRecordingResult(task_id); +} + +void HangoutPubSubClient::OnRecordingPublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + SignalPublishRecordingError(task_id, stanza); +} + +void HangoutPubSubClient::OnMediaBlockStateChange( + const PubSubStateChange& change) { + const std::string& blockee_nick = change.published_nick; + const std::string& blocker_nick = change.publisher_nick; + + bool was_blockee = change.old_state; + bool is_blockee = change.new_state; + if (!was_blockee && is_blockee) { + SignalMediaBlock(blockee_nick, blocker_nick); + } + // TODO: Should we bother signaling unblock? Currently + // it isn't allowed, but it might happen when a participant leaves + // the room and the item is retracted. +} + +void HangoutPubSubClient::OnMediaBlockPublishResult( + const std::string& task_id, const XmlElement* item) { + const std::string& blockee_nick = GetBlockeeNickFromItem(item); + SignalMediaBlockResult(task_id, blockee_nick); +} + +void HangoutPubSubClient::OnMediaBlockPublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + const std::string& blockee_nick = GetBlockeeNickFromItem(item); + SignalMediaBlockError(task_id, blockee_nick, stanza); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/hangoutpubsubclient.h b/webrtc/libjingle/xmpp/hangoutpubsubclient.h new file mode 100644 index 0000000000..fecc727604 --- /dev/null +++ b/webrtc/libjingle/xmpp/hangoutpubsubclient.h @@ -0,0 +1,178 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_HANGOUTPUBSUBCLIENT_H_ +#define WEBRTC_LIBJINGLE_XMPP_HANGOUTPUBSUBCLIENT_H_ + +#include +#include +#include +#include + +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubclient.h" +#include "webrtc/libjingle/xmpp/pubsubstateclient.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/sigslotrepeater.h" + +// Gives a high-level API for MUC call PubSub needs such as +// presenter state, recording state, mute state, and remote mute. + +namespace buzz { + +class Jid; +class XmlElement; +class XmppTaskParentInterface; + +// A client tied to a specific MUC jid and local nick. Provides ways +// to get updates and publish state and events. Must call +// RequestAll() to start getting updates. +class HangoutPubSubClient : public sigslot::has_slots<> { + public: + HangoutPubSubClient(XmppTaskParentInterface* parent, + const Jid& mucjid, + const std::string& nick); + ~HangoutPubSubClient(); + const Jid& mucjid() const { return mucjid_; } + const std::string& nick() const { return nick_; } + + // Requests all of the different states and subscribes for updates. + // Responses and updates will be signalled via the various signals. + void RequestAll(); + // Signal (nick, was_presenting, is_presenting) + sigslot::signal3 SignalPresenterStateChange; + // Signal (nick, was_muted, is_muted) + sigslot::signal3 SignalAudioMuteStateChange; + // Signal (nick, was_muted, is_muted) + sigslot::signal3 SignalVideoMuteStateChange; + // Signal (nick, was_paused, is_paused) + sigslot::signal3 SignalVideoPauseStateChange; + // Signal (nick, was_recording, is_recording) + sigslot::signal3 SignalRecordingStateChange; + // Signal (mutee_nick, muter_nick, should_mute_locally) + sigslot::signal3 SignalRemoteMute; + // Signal (blockee_nick, blocker_nick) + sigslot::signal2 SignalMediaBlock; + + // Signal (node, error stanza) + sigslot::signal2 SignalRequestError; + + // On each of these, provide a task_id_out to get the task_id, which + // can be correlated to the error and result signals. + void PublishPresenterState( + bool presenting, std::string* task_id_out = NULL); + void PublishAudioMuteState( + bool muted, std::string* task_id_out = NULL); + void PublishVideoMuteState( + bool muted, std::string* task_id_out = NULL); + void PublishVideoPauseState( + bool paused, std::string* task_id_out = NULL); + void PublishRecordingState( + bool recording, std::string* task_id_out = NULL); + void RemoteMute( + const std::string& mutee_nick, std::string* task_id_out = NULL); + void BlockMedia( + const std::string& blockee_nick, std::string* task_id_out = NULL); + + // Signal task_id + sigslot::signal1 SignalPublishAudioMuteResult; + sigslot::signal1 SignalPublishVideoMuteResult; + sigslot::signal1 SignalPublishVideoPauseResult; + sigslot::signal1 SignalPublishPresenterResult; + sigslot::signal1 SignalPublishRecordingResult; + // Signal (task_id, mutee_nick) + sigslot::signal2 SignalRemoteMuteResult; + // Signal (task_id, blockee_nick) + sigslot::signal2 SignalMediaBlockResult; + + // Signal (task_id, error stanza) + sigslot::signal2 SignalPublishAudioMuteError; + sigslot::signal2 SignalPublishVideoMuteError; + sigslot::signal2 SignalPublishVideoPauseError; + sigslot::signal2 SignalPublishPresenterError; + sigslot::signal2 SignalPublishRecordingError; + sigslot::signal2 SignalPublishMediaBlockError; + // Signal (task_id, mutee_nick, error stanza) + sigslot::signal3 SignalRemoteMuteError; + // Signal (task_id, blockee_nick, error stanza) + sigslot::signal3 SignalMediaBlockError; + + + private: + void OnPresenterRequestError(PubSubClient* client, + const XmlElement* stanza); + void OnMediaRequestError(PubSubClient* client, + const XmlElement* stanza); + + void OnPresenterStateChange(const PubSubStateChange& change); + void OnPresenterPublishResult(const std::string& task_id, + const XmlElement* item); + void OnPresenterPublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + void OnAudioMuteStateChange(const PubSubStateChange& change); + void OnAudioMutePublishResult(const std::string& task_id, + const XmlElement* item); + void OnAudioMutePublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + void OnVideoMuteStateChange(const PubSubStateChange& change); + void OnVideoMutePublishResult(const std::string& task_id, + const XmlElement* item); + void OnVideoMutePublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + void OnVideoPauseStateChange(const PubSubStateChange& change); + void OnVideoPausePublishResult(const std::string& task_id, + const XmlElement* item); + void OnVideoPausePublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + void OnRecordingStateChange(const PubSubStateChange& change); + void OnRecordingPublishResult(const std::string& task_id, + const XmlElement* item); + void OnRecordingPublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + void OnMediaBlockStateChange(const PubSubStateChange& change); + void OnMediaBlockPublishResult(const std::string& task_id, + const XmlElement* item); + void OnMediaBlockPublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + Jid mucjid_; + std::string nick_; + std::unique_ptr media_client_; + std::unique_ptr presenter_client_; + std::unique_ptr > presenter_state_client_; + std::unique_ptr > audio_mute_state_client_; + std::unique_ptr > video_mute_state_client_; + std::unique_ptr > video_pause_state_client_; + std::unique_ptr > recording_state_client_; + std::unique_ptr > media_block_state_client_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_HANGOUTPUBSUBCLIENT_H_ diff --git a/webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc b/webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc new file mode 100644 index 0000000000..dd706b6a9a --- /dev/null +++ b/webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc @@ -0,0 +1,754 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class TestHangoutPubSubListener : public sigslot::has_slots<> { + public: + TestHangoutPubSubListener() : + request_error_count(0), + publish_audio_mute_error_count(0), + publish_video_mute_error_count(0), + publish_video_pause_error_count(0), + publish_presenter_error_count(0), + publish_recording_error_count(0), + remote_mute_error_count(0) { + } + + void OnPresenterStateChange( + const std::string& nick, bool was_presenting, bool is_presenting) { + last_presenter_nick = nick; + last_was_presenting = was_presenting; + last_is_presenting = is_presenting; + } + + void OnAudioMuteStateChange( + const std::string& nick, bool was_muted, bool is_muted) { + last_audio_muted_nick = nick; + last_was_audio_muted = was_muted; + last_is_audio_muted = is_muted; + } + + void OnVideoMuteStateChange( + const std::string& nick, bool was_muted, bool is_muted) { + last_video_muted_nick = nick; + last_was_video_muted = was_muted; + last_is_video_muted = is_muted; + } + + void OnVideoPauseStateChange( + const std::string& nick, bool was_paused, bool is_paused) { + last_video_paused_nick = nick; + last_was_video_paused = was_paused; + last_is_video_paused = is_paused; + } + + void OnRecordingStateChange( + const std::string& nick, bool was_recording, bool is_recording) { + last_recording_nick = nick; + last_was_recording = was_recording; + last_is_recording = is_recording; + } + + void OnRemoteMute( + const std::string& mutee_nick, + const std::string& muter_nick, + bool should_mute_locally) { + last_mutee_nick = mutee_nick; + last_muter_nick = muter_nick; + last_should_mute = should_mute_locally; + } + + void OnMediaBlock( + const std::string& blockee_nick, + const std::string& blocker_nick) { + last_blockee_nick = blockee_nick; + last_blocker_nick = blocker_nick; + } + + void OnRequestError(const std::string& node, const buzz::XmlElement* stanza) { + ++request_error_count; + request_error_node = node; + } + + void OnPublishAudioMuteError(const std::string& task_id, + const buzz::XmlElement* stanza) { + ++publish_audio_mute_error_count; + error_task_id = task_id; + } + + void OnPublishVideoMuteError(const std::string& task_id, + const buzz::XmlElement* stanza) { + ++publish_video_mute_error_count; + error_task_id = task_id; + } + + void OnPublishVideoPauseError(const std::string& task_id, + const buzz::XmlElement* stanza) { + ++publish_video_pause_error_count; + error_task_id = task_id; + } + + void OnPublishPresenterError(const std::string& task_id, + const buzz::XmlElement* stanza) { + ++publish_presenter_error_count; + error_task_id = task_id; + } + + void OnPublishRecordingError(const std::string& task_id, + const buzz::XmlElement* stanza) { + ++publish_recording_error_count; + error_task_id = task_id; + } + + void OnRemoteMuteResult(const std::string& task_id, + const std::string& mutee_nick) { + result_task_id = task_id; + remote_mute_mutee_nick = mutee_nick; + } + + void OnRemoteMuteError(const std::string& task_id, + const std::string& mutee_nick, + const buzz::XmlElement* stanza) { + ++remote_mute_error_count; + error_task_id = task_id; + remote_mute_mutee_nick = mutee_nick; + } + + void OnMediaBlockResult(const std::string& task_id, + const std::string& blockee_nick) { + result_task_id = task_id; + media_blockee_nick = blockee_nick; + } + + void OnMediaBlockError(const std::string& task_id, + const std::string& blockee_nick, + const buzz::XmlElement* stanza) { + ++media_block_error_count; + error_task_id = task_id; + media_blockee_nick = blockee_nick; + } + + std::string last_presenter_nick; + bool last_is_presenting; + bool last_was_presenting; + std::string last_audio_muted_nick; + bool last_is_audio_muted; + bool last_was_audio_muted; + std::string last_video_muted_nick; + bool last_is_video_muted; + bool last_was_video_muted; + std::string last_video_paused_nick; + bool last_is_video_paused; + bool last_was_video_paused; + std::string last_recording_nick; + bool last_is_recording; + bool last_was_recording; + std::string last_mutee_nick; + std::string last_muter_nick; + bool last_should_mute; + std::string last_blockee_nick; + std::string last_blocker_nick; + + int request_error_count; + std::string request_error_node; + int publish_audio_mute_error_count; + int publish_video_mute_error_count; + int publish_video_pause_error_count; + int publish_presenter_error_count; + int publish_recording_error_count; + int remote_mute_error_count; + std::string result_task_id; + std::string error_task_id; + std::string remote_mute_mutee_nick; + int media_block_error_count; + std::string media_blockee_nick; +}; + +class HangoutPubSubClientTest : public testing::Test { + public: + HangoutPubSubClientTest() : + pubsubjid("room@domain.com"), + nick("me") { + + runner.reset(new rtc::FakeTaskRunner()); + xmpp_client = new buzz::FakeXmppClient(runner.get()); + client.reset(new buzz::HangoutPubSubClient(xmpp_client, pubsubjid, nick)); + listener.reset(new TestHangoutPubSubListener()); + client->SignalPresenterStateChange.connect( + listener.get(), &TestHangoutPubSubListener::OnPresenterStateChange); + client->SignalAudioMuteStateChange.connect( + listener.get(), &TestHangoutPubSubListener::OnAudioMuteStateChange); + client->SignalVideoMuteStateChange.connect( + listener.get(), &TestHangoutPubSubListener::OnVideoMuteStateChange); + client->SignalVideoPauseStateChange.connect( + listener.get(), &TestHangoutPubSubListener::OnVideoPauseStateChange); + client->SignalRecordingStateChange.connect( + listener.get(), &TestHangoutPubSubListener::OnRecordingStateChange); + client->SignalRemoteMute.connect( + listener.get(), &TestHangoutPubSubListener::OnRemoteMute); + client->SignalMediaBlock.connect( + listener.get(), &TestHangoutPubSubListener::OnMediaBlock); + client->SignalRequestError.connect( + listener.get(), &TestHangoutPubSubListener::OnRequestError); + client->SignalPublishAudioMuteError.connect( + listener.get(), &TestHangoutPubSubListener::OnPublishAudioMuteError); + client->SignalPublishVideoMuteError.connect( + listener.get(), &TestHangoutPubSubListener::OnPublishVideoMuteError); + client->SignalPublishVideoPauseError.connect( + listener.get(), &TestHangoutPubSubListener::OnPublishVideoPauseError); + client->SignalPublishPresenterError.connect( + listener.get(), &TestHangoutPubSubListener::OnPublishPresenterError); + client->SignalPublishRecordingError.connect( + listener.get(), &TestHangoutPubSubListener::OnPublishRecordingError); + client->SignalRemoteMuteResult.connect( + listener.get(), &TestHangoutPubSubListener::OnRemoteMuteResult); + client->SignalRemoteMuteError.connect( + listener.get(), &TestHangoutPubSubListener::OnRemoteMuteError); + client->SignalMediaBlockResult.connect( + listener.get(), &TestHangoutPubSubListener::OnMediaBlockResult); + client->SignalMediaBlockError.connect( + listener.get(), &TestHangoutPubSubListener::OnMediaBlockError); + } + + std::unique_ptr runner; + // xmpp_client deleted by deleting runner. + buzz::FakeXmppClient* xmpp_client; + std::unique_ptr client; + std::unique_ptr listener; + buzz::Jid pubsubjid; + std::string nick; +}; + +TEST_F(HangoutPubSubClientTest, TestRequest) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + client->RequestAll(); + std::string expected_presenter_request = + "" + "" + "" + "" + ""; + + std::string expected_media_request = + "" + "" + "" + "" + ""; + + ASSERT_EQ(2U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_presenter_request, xmpp_client->sent_stanzas()[0]->Str()); + EXPECT_EQ(expected_media_request, xmpp_client->sent_stanzas()[1]->Str()); + + std::string presenter_response = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + // Some clients are "bad" in that they'll jam multiple states in + // all at once. We have to deal with it. + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(presenter_response)); + EXPECT_EQ("presenting-nick", listener->last_presenter_nick); + EXPECT_FALSE(listener->last_was_presenting); + EXPECT_TRUE(listener->last_is_presenting); + + std::string media_response = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(media_response)); + EXPECT_EQ("muted-nick", listener->last_audio_muted_nick); + EXPECT_FALSE(listener->last_was_audio_muted); + EXPECT_TRUE(listener->last_is_audio_muted); + + EXPECT_EQ("video-muted-nick", listener->last_video_muted_nick); + EXPECT_FALSE(listener->last_was_video_muted); + EXPECT_TRUE(listener->last_is_video_muted); + + EXPECT_EQ("video-paused-nick", listener->last_video_paused_nick); + EXPECT_FALSE(listener->last_was_video_paused); + EXPECT_TRUE(listener->last_is_video_paused); + + EXPECT_EQ("recording-nick", listener->last_recording_nick); + EXPECT_FALSE(listener->last_was_recording); + EXPECT_TRUE(listener->last_is_recording); + + std::string incoming_presenter_resets_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_presenter_resets_message)); + EXPECT_EQ("presenting-nick", listener->last_presenter_nick); + //EXPECT_TRUE(listener->last_was_presenting); + EXPECT_FALSE(listener->last_is_presenting); + + std::string incoming_presenter_retracts_message = + "" + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_presenter_retracts_message)); + EXPECT_EQ("presenting-nick2", listener->last_presenter_nick); + EXPECT_TRUE(listener->last_was_presenting); + EXPECT_FALSE(listener->last_is_presenting); + + std::string incoming_media_retracts_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_media_retracts_message)); + EXPECT_EQ("muted-nick", listener->last_audio_muted_nick); + EXPECT_TRUE(listener->last_was_audio_muted); + EXPECT_FALSE(listener->last_is_audio_muted); + + EXPECT_EQ("video-paused-nick", listener->last_video_paused_nick); + EXPECT_TRUE(listener->last_was_video_paused); + EXPECT_FALSE(listener->last_is_video_paused); + + EXPECT_EQ("recording-nick", listener->last_recording_nick); + EXPECT_TRUE(listener->last_was_recording); + EXPECT_FALSE(listener->last_is_recording); + + std::string incoming_presenter_changes_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_presenter_changes_message)); + EXPECT_EQ("presenting-nick2", listener->last_presenter_nick); + EXPECT_FALSE(listener->last_was_presenting); + EXPECT_TRUE(listener->last_is_presenting); + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_presenter_changes_message)); + EXPECT_EQ("presenting-nick2", listener->last_presenter_nick); + EXPECT_TRUE(listener->last_was_presenting); + EXPECT_TRUE(listener->last_is_presenting); + + std::string incoming_media_changes_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_media_changes_message)); + EXPECT_EQ("muted-nick2", listener->last_audio_muted_nick); + EXPECT_FALSE(listener->last_was_audio_muted); + EXPECT_TRUE(listener->last_is_audio_muted); + + EXPECT_EQ("video-paused-nick2", listener->last_video_paused_nick); + EXPECT_FALSE(listener->last_was_video_paused); + EXPECT_TRUE(listener->last_is_video_paused); + + EXPECT_EQ("recording-nick2", listener->last_recording_nick); + EXPECT_FALSE(listener->last_was_recording); + EXPECT_TRUE(listener->last_is_recording); + + std::string incoming_remote_mute_message = + "" + " " + " " + " " + " " + " " + " " + " " + ""; + + listener->last_is_audio_muted = false; + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_remote_mute_message)); + EXPECT_EQ("mutee", listener->last_mutee_nick); + EXPECT_EQ("muter", listener->last_muter_nick); + EXPECT_FALSE(listener->last_should_mute); + EXPECT_EQ("mutee", listener->last_audio_muted_nick); + EXPECT_TRUE(listener->last_is_audio_muted); + + std::string incoming_remote_mute_me_message = + "" + " " + " " + " " + " " + " " + " " + " " + ""; + + listener->last_is_audio_muted = false; + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_remote_mute_me_message)); + EXPECT_EQ("me", listener->last_mutee_nick); + EXPECT_EQ("muter", listener->last_muter_nick); + EXPECT_TRUE(listener->last_should_mute); + EXPECT_EQ("me", listener->last_audio_muted_nick); + EXPECT_TRUE(listener->last_is_audio_muted); + + std::string incoming_media_block_message = + "" + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_media_block_message)); + EXPECT_EQ("blockee", listener->last_blockee_nick); + EXPECT_EQ("blocker", listener->last_blocker_nick); +} + +TEST_F(HangoutPubSubClientTest, TestRequestError) { + client->RequestAll(); + std::string result_iq = + "" + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->request_error_count); + EXPECT_EQ("google:presenter", listener->request_error_node); +} + +TEST_F(HangoutPubSubClientTest, TestPublish) { + client->PublishPresenterState(true); + std::string expected_presenter_iq = + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_presenter_iq, + xmpp_client->sent_stanzas()[0]->Str()); + + client->PublishAudioMuteState(true); + std::string expected_audio_mute_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(2U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_audio_mute_iq, xmpp_client->sent_stanzas()[1]->Str()); + + client->PublishVideoPauseState(true); + std::string expected_video_pause_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(3U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_video_pause_iq, xmpp_client->sent_stanzas()[2]->Str()); + + client->PublishRecordingState(true); + std::string expected_recording_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(4U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_recording_iq, xmpp_client->sent_stanzas()[3]->Str()); + + client->RemoteMute("mutee"); + std::string expected_remote_mute_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(5U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_remote_mute_iq, xmpp_client->sent_stanzas()[4]->Str()); + + client->PublishPresenterState(false); + std::string expected_presenter_retract_iq = + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(6U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_presenter_retract_iq, + xmpp_client->sent_stanzas()[5]->Str()); + + client->PublishAudioMuteState(false); + std::string expected_audio_mute_retract_iq = + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(7U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_audio_mute_retract_iq, + xmpp_client->sent_stanzas()[6]->Str()); + + client->PublishVideoPauseState(false); + std::string expected_video_pause_retract_iq = + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(8U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_video_pause_retract_iq, + xmpp_client->sent_stanzas()[7]->Str()); + + client->BlockMedia("blockee"); + std::string expected_media_block_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(9U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_media_block_iq, xmpp_client->sent_stanzas()[8]->Str()); +} + +TEST_F(HangoutPubSubClientTest, TestPublishPresenterError) { + std::string result_iq = + ""; + + client->PublishPresenterState(true); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->publish_presenter_error_count); + EXPECT_EQ("0", listener->error_task_id); +} + + +TEST_F(HangoutPubSubClientTest, TestPublishAudioMuteError) { + std::string result_iq = + ""; + + client->PublishAudioMuteState(true); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->publish_audio_mute_error_count); + EXPECT_EQ("0", listener->error_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestPublishVideoPauseError) { + std::string result_iq = + ""; + + client->PublishVideoPauseState(true); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->publish_video_pause_error_count); + EXPECT_EQ("0", listener->error_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestPublishRecordingError) { + std::string result_iq = + ""; + + client->PublishRecordingState(true); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->publish_recording_error_count); + EXPECT_EQ("0", listener->error_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestPublishRemoteMuteResult) { + std::string result_iq = + ""; + + client->RemoteMute("joe"); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ("joe", listener->remote_mute_mutee_nick); + EXPECT_EQ("0", listener->result_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestRemoteMuteError) { + std::string result_iq = + ""; + + client->RemoteMute("joe"); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->remote_mute_error_count); + EXPECT_EQ("joe", listener->remote_mute_mutee_nick); + EXPECT_EQ("0", listener->error_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestPublishMediaBlockResult) { + std::string result_iq = + ""; + + client->BlockMedia("joe"); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ("joe", listener->media_blockee_nick); + EXPECT_EQ("0", listener->result_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestMediaBlockError) { + std::string result_iq = + ""; + + client->BlockMedia("joe"); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->remote_mute_error_count); + EXPECT_EQ("joe", listener->media_blockee_nick); + EXPECT_EQ("0", listener->error_task_id); +} diff --git a/webrtc/libjingle/xmpp/iqtask.cc b/webrtc/libjingle/xmpp/iqtask.cc new file mode 100644 index 0000000000..a7a41ee519 --- /dev/null +++ b/webrtc/libjingle/xmpp/iqtask.cc @@ -0,0 +1,69 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/iqtask.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" + +namespace buzz { + +static const int kDefaultIqTimeoutSecs = 15; + +IqTask::IqTask(XmppTaskParentInterface* parent, + const std::string& verb, + const buzz::Jid& to, + buzz::XmlElement* el) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE), + to_(to), + stanza_(MakeIq(verb, to_, task_id())) { + stanza_->AddElement(el); + set_timeout_seconds(kDefaultIqTimeoutSecs); +} + +int IqTask::ProcessStart() { + buzz::XmppReturnStatus ret = SendStanza(stanza_.get()); + // TODO: HandleError(NULL) if SendStanza fails? + return (ret == buzz::XMPP_RETURN_OK) ? STATE_RESPONSE : STATE_ERROR; +} + +bool IqTask::HandleStanza(const buzz::XmlElement* stanza) { + if (!MatchResponseIq(stanza, to_, task_id())) + return false; + + if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_RESULT && + stanza->Attr(buzz::QN_TYPE) != buzz::STR_ERROR) { + return false; + } + + QueueStanza(stanza); + return true; +} + +int IqTask::ProcessResponse() { + const buzz::XmlElement* stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + + bool success = (stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT); + if (success) { + HandleResult(stanza); + } else { + SignalError(this, stanza->FirstNamed(QN_ERROR)); + } + return STATE_DONE; +} + +int IqTask::OnTimeout() { + SignalError(this, NULL); + return XmppTask::OnTimeout(); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/iqtask.h b/webrtc/libjingle/xmpp/iqtask.h new file mode 100644 index 0000000000..0db00711e8 --- /dev/null +++ b/webrtc/libjingle/xmpp/iqtask.h @@ -0,0 +1,49 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_IQTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_IQTASK_H_ + +#include +#include + +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +class IqTask : public XmppTask { + public: + IqTask(XmppTaskParentInterface* parent, + const std::string& verb, const Jid& to, + XmlElement* el); + virtual ~IqTask() {} + + const XmlElement* stanza() const { return stanza_.get(); } + + sigslot::signal2 SignalError; + + protected: + virtual void HandleResult(const XmlElement* element) = 0; + + private: + virtual int ProcessStart(); + virtual bool HandleStanza(const XmlElement* stanza); + virtual int ProcessResponse(); + virtual int OnTimeout(); + + Jid to_; + std::unique_ptr stanza_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_IQTASK_H_ diff --git a/webrtc/libjingle/xmpp/jid.cc b/webrtc/libjingle/xmpp/jid.cc new file mode 100644 index 0000000000..ad05380568 --- /dev/null +++ b/webrtc/libjingle/xmpp/jid.cc @@ -0,0 +1,379 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/jid.h" + +#include + +#include +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +namespace buzz { + +Jid::Jid() { +} + +Jid::Jid(const std::string& jid_string) { + if (jid_string.empty()) + return; + + // First find the slash and slice off that part + size_t slash = jid_string.find('/'); + resource_name_ = (slash == std::string::npos ? STR_EMPTY : + jid_string.substr(slash + 1)); + + // Now look for the node + size_t at = jid_string.find('@'); + size_t domain_begin; + if (at < slash && at != std::string::npos) { + node_name_ = jid_string.substr(0, at); + domain_begin = at + 1; + } else { + domain_begin = 0; + } + + // Now take what is left as the domain + size_t domain_length = (slash == std::string::npos) ? + (jid_string.length() - domain_begin) : (slash - domain_begin); + domain_name_ = jid_string.substr(domain_begin, domain_length); + + ValidateOrReset(); +} + +Jid::Jid(const std::string& node_name, + const std::string& domain_name, + const std::string& resource_name) + : node_name_(node_name), + domain_name_(domain_name), + resource_name_(resource_name) { + ValidateOrReset(); +} + +void Jid::ValidateOrReset() { + bool valid_node; + bool valid_domain; + bool valid_resource; + + node_name_ = PrepNode(node_name_, &valid_node); + domain_name_ = PrepDomain(domain_name_, &valid_domain); + resource_name_ = PrepResource(resource_name_, &valid_resource); + + if (!valid_node || !valid_domain || !valid_resource) { + node_name_.clear(); + domain_name_.clear(); + resource_name_.clear(); + } +} + +std::string Jid::Str() const { + if (!IsValid()) + return STR_EMPTY; + + std::string ret; + + if (!node_name_.empty()) + ret = node_name_ + "@"; + + ASSERT(domain_name_ != STR_EMPTY); + ret += domain_name_; + + if (!resource_name_.empty()) + ret += "/" + resource_name_; + + return ret; +} + +Jid::~Jid() { +} + +bool Jid::IsEmpty() const { + return (node_name_.empty() && domain_name_.empty() && + resource_name_.empty()); +} + +bool Jid::IsValid() const { + return !domain_name_.empty(); +} + +bool Jid::IsBare() const { + if (IsEmpty()) { + LOG(LS_VERBOSE) << "Warning: Calling IsBare() on the empty jid."; + return true; + } + return IsValid() && resource_name_.empty(); +} + +bool Jid::IsFull() const { + return IsValid() && !resource_name_.empty(); +} + +Jid Jid::BareJid() const { + if (!IsValid()) + return Jid(); + if (!IsFull()) + return *this; + return Jid(node_name_, domain_name_, STR_EMPTY); +} + +bool Jid::BareEquals(const Jid& other) const { + return other.node_name_ == node_name_ && + other.domain_name_ == domain_name_; +} + +void Jid::CopyFrom(const Jid& jid) { + this->node_name_ = jid.node_name_; + this->domain_name_ = jid.domain_name_; + this->resource_name_ = jid.resource_name_; +} + +bool Jid::operator==(const Jid& other) const { + return other.node_name_ == node_name_ && + other.domain_name_ == domain_name_ && + other.resource_name_ == resource_name_; +} + +int Jid::Compare(const Jid& other) const { + int compare_result; + compare_result = node_name_.compare(other.node_name_); + if (0 != compare_result) + return compare_result; + compare_result = domain_name_.compare(other.domain_name_); + if (0 != compare_result) + return compare_result; + compare_result = resource_name_.compare(other.resource_name_); + return compare_result; +} + +// --- JID parsing code: --- + +// Checks and normalizes the node part of a JID. +std::string Jid::PrepNode(const std::string& node, bool* valid) { + *valid = false; + std::string result; + + for (std::string::const_iterator i = node.begin(); i < node.end(); ++i) { + bool char_valid = true; + unsigned char ch = *i; + if (ch <= 0x7F) { + result += PrepNodeAscii(ch, &char_valid); + } + else { + // TODO: implement the correct stringprep protocol for these + result += tolower(ch); + } + if (!char_valid) { + return STR_EMPTY; + } + } + + if (result.length() > 1023) { + return STR_EMPTY; + } + *valid = true; + return result; +} + + +// Returns the appropriate mapping for an ASCII character in a node. +char Jid::PrepNodeAscii(char ch, bool* valid) { + *valid = true; + switch (ch) { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + return (char)(ch + ('a' - 'A')); + + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: + case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: + case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: + case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case ' ': case '&': case '/': case ':': case '<': case '>': case '@': + case '\"': case '\'': + case 0x7F: + *valid = false; + return 0; + + default: + return ch; + } +} + + +// Checks and normalizes the resource part of a JID. +std::string Jid::PrepResource(const std::string& resource, bool* valid) { + *valid = false; + std::string result; + + for (std::string::const_iterator i = resource.begin(); + i < resource.end(); ++i) { + bool char_valid = true; + unsigned char ch = *i; + if (ch <= 0x7F) { + result += PrepResourceAscii(ch, &char_valid); + } + else { + // TODO: implement the correct stringprep protocol for these + result += ch; + } + } + + if (result.length() > 1023) { + return STR_EMPTY; + } + *valid = true; + return result; +} + +// Returns the appropriate mapping for an ASCII character in a resource. +char Jid::PrepResourceAscii(char ch, bool* valid) { + *valid = true; + switch (ch) { + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: + case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: + case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: + case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x7F: + *valid = false; + return 0; + + default: + return ch; + } +} + +// Checks and normalizes the domain part of a JID. +std::string Jid::PrepDomain(const std::string& domain, bool* valid) { + *valid = false; + std::string result; + + // TODO: if the domain contains a ':', then we should parse it + // as an IPv6 address rather than giving an error about illegal domain. + PrepDomain(domain, &result, valid); + if (!*valid) { + return STR_EMPTY; + } + + if (result.length() > 1023) { + return STR_EMPTY; + } + *valid = true; + return result; +} + + +// Checks and normalizes an IDNA domain. +void Jid::PrepDomain(const std::string& domain, std::string* buf, bool* valid) { + *valid = false; + std::string::const_iterator last = domain.begin(); + for (std::string::const_iterator i = domain.begin(); i < domain.end(); ++i) { + bool label_valid = true; + char ch = *i; + switch (ch) { + case 0x002E: +#if 0 // FIX: This isn't UTF-8-aware. + case 0x3002: + case 0xFF0E: + case 0xFF61: +#endif + PrepDomainLabel(last, i, buf, &label_valid); + *buf += '.'; + last = i + 1; + break; + } + if (!label_valid) { + return; + } + } + PrepDomainLabel(last, domain.end(), buf, valid); +} + +// Checks and normalizes a domain label. +void Jid::PrepDomainLabel( + std::string::const_iterator start, std::string::const_iterator end, + std::string* buf, bool* valid) { + *valid = false; + + int start_len = static_cast(buf->length()); + for (std::string::const_iterator i = start; i < end; ++i) { + bool char_valid = true; + unsigned char ch = *i; + if (ch <= 0x7F) { + *buf += PrepDomainLabelAscii(ch, &char_valid); + } + else { + // TODO: implement ToASCII for these + *buf += ch; + } + if (!char_valid) { + return; + } + } + + int count = static_cast(buf->length() - start_len); + if (count == 0) { + return; + } + else if (count > 63) { + return; + } + + // Is this check needed? See comment in PrepDomainLabelAscii. + if ((*buf)[start_len] == '-') { + return; + } + if ((*buf)[buf->length() - 1] == '-') { + return; + } + *valid = true; +} + + +// Returns the appropriate mapping for an ASCII character in a domain label. +char Jid::PrepDomainLabelAscii(char ch, bool* valid) { + *valid = true; + // TODO: A literal reading of the spec seems to say that we do + // not need to check for these illegal characters (an "internationalized + // domain label" runs ToASCII with UseSTD3... set to false). But that + // can't be right. We should at least be checking that there are no '/' + // or '@' characters in the domain. Perhaps we should see what others + // do in this case. + + switch (ch) { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + return (char)(ch + ('a' - 'A')); + + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: + case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: + case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: + case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: + case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23: + case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: + case 0x2A: case 0x2B: case 0x2C: case 0x2E: case 0x2F: case 0x3A: + case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: + case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60: + case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: + *valid = false; + return 0; + + default: + return ch; + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/jid.h b/webrtc/libjingle/xmpp/jid.h new file mode 100644 index 0000000000..83e27cc6df --- /dev/null +++ b/webrtc/libjingle/xmpp/jid.h @@ -0,0 +1,80 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_JID_H_ +#define WEBRTC_LIBJINGLE_XMPP_JID_H_ + +#include +#include "webrtc/libjingle/xmllite/xmlconstants.h" + +namespace buzz { + +// The Jid class encapsulates and provides parsing help for Jids. A Jid +// consists of three parts: the node, the domain and the resource, e.g.: +// +// node@domain/resource +// +// The node and resource are both optional. A valid jid is defined to have +// a domain. A bare jid is defined to not have a resource and a full jid +// *does* have a resource. +class Jid { +public: + explicit Jid(); + explicit Jid(const std::string& jid_string); + explicit Jid(const std::string& node_name, + const std::string& domain_name, + const std::string& resource_name); + ~Jid(); + + const std::string & node() const { return node_name_; } + const std::string & domain() const { return domain_name_; } + const std::string & resource() const { return resource_name_; } + + std::string Str() const; + Jid BareJid() const; + + bool IsEmpty() const; + bool IsValid() const; + bool IsBare() const; + bool IsFull() const; + + bool BareEquals(const Jid& other) const; + void CopyFrom(const Jid& jid); + bool operator==(const Jid& other) const; + bool operator!=(const Jid& other) const { return !operator==(other); } + + bool operator<(const Jid& other) const { return Compare(other) < 0; }; + bool operator>(const Jid& other) const { return Compare(other) > 0; }; + + int Compare(const Jid & other) const; + +private: + void ValidateOrReset(); + + static std::string PrepNode(const std::string& node, bool* valid); + static char PrepNodeAscii(char ch, bool* valid); + static std::string PrepResource(const std::string& start, bool* valid); + static char PrepResourceAscii(char ch, bool* valid); + static std::string PrepDomain(const std::string& domain, bool* valid); + static void PrepDomain(const std::string& domain, + std::string* buf, bool* valid); + static void PrepDomainLabel( + std::string::const_iterator start, std::string::const_iterator end, + std::string* buf, bool* valid); + static char PrepDomainLabelAscii(char ch, bool *valid); + + std::string node_name_; + std::string domain_name_; + std::string resource_name_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_JID_H_ diff --git a/webrtc/libjingle/xmpp/jid_unittest.cc b/webrtc/libjingle/xmpp/jid_unittest.cc new file mode 100644 index 0000000000..e22f6a2644 --- /dev/null +++ b/webrtc/libjingle/xmpp/jid_unittest.cc @@ -0,0 +1,122 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/base/gunit.h" + +using buzz::Jid; + +TEST(JidTest, TestDomain) { + Jid jid("dude"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("dude", jid.domain()); + EXPECT_EQ("", jid.resource()); + EXPECT_EQ("dude", jid.Str()); + EXPECT_EQ("dude", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_TRUE(jid.IsBare()); + EXPECT_FALSE(jid.IsFull()); +} + +TEST(JidTest, TestNodeDomain) { + Jid jid("walter@dude"); + EXPECT_EQ("walter", jid.node()); + EXPECT_EQ("dude", jid.domain()); + EXPECT_EQ("", jid.resource()); + EXPECT_EQ("walter@dude", jid.Str()); + EXPECT_EQ("walter@dude", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_TRUE(jid.IsBare()); + EXPECT_FALSE(jid.IsFull()); +} + +TEST(JidTest, TestDomainResource) { + Jid jid("dude/bowlingalley"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("dude", jid.domain()); + EXPECT_EQ("bowlingalley", jid.resource()); + EXPECT_EQ("dude/bowlingalley", jid.Str()); + EXPECT_EQ("dude", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_FALSE(jid.IsBare()); + EXPECT_TRUE(jid.IsFull()); +} + +TEST(JidTest, TestNodeDomainResource) { + Jid jid("walter@dude/bowlingalley"); + EXPECT_EQ("walter", jid.node()); + EXPECT_EQ("dude", jid.domain()); + EXPECT_EQ("bowlingalley", jid.resource()); + EXPECT_EQ("walter@dude/bowlingalley", jid.Str()); + EXPECT_EQ("walter@dude", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_FALSE(jid.IsBare()); + EXPECT_TRUE(jid.IsFull()); +} + +TEST(JidTest, TestNode) { + Jid jid("walter@"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("", jid.domain()); + EXPECT_EQ("", jid.resource()); + EXPECT_EQ("", jid.Str()); + EXPECT_EQ("", jid.BareJid().Str()); + EXPECT_FALSE(jid.IsValid()); + EXPECT_TRUE(jid.IsBare()); + EXPECT_FALSE(jid.IsFull()); +} + +TEST(JidTest, TestResource) { + Jid jid("/bowlingalley"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("", jid.domain()); + EXPECT_EQ("", jid.resource()); + EXPECT_EQ("", jid.Str()); + EXPECT_EQ("", jid.BareJid().Str()); + EXPECT_FALSE(jid.IsValid()); + EXPECT_TRUE(jid.IsBare()); + EXPECT_FALSE(jid.IsFull()); +} + +TEST(JidTest, TestNodeResource) { + Jid jid("walter@/bowlingalley"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("", jid.domain()); + EXPECT_EQ("", jid.resource()); + EXPECT_EQ("", jid.Str()); + EXPECT_EQ("", jid.BareJid().Str()); + EXPECT_FALSE(jid.IsValid()); + EXPECT_TRUE(jid.IsBare()); + EXPECT_FALSE(jid.IsFull()); +} + +TEST(JidTest, TestFunky) { + Jid jid("bowling@muchat/walter@dude"); + EXPECT_EQ("bowling", jid.node()); + EXPECT_EQ("muchat", jid.domain()); + EXPECT_EQ("walter@dude", jid.resource()); + EXPECT_EQ("bowling@muchat/walter@dude", jid.Str()); + EXPECT_EQ("bowling@muchat", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_FALSE(jid.IsBare()); + EXPECT_TRUE(jid.IsFull()); +} + +TEST(JidTest, TestFunky2) { + Jid jid("muchat/walter@dude"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("muchat", jid.domain()); + EXPECT_EQ("walter@dude", jid.resource()); + EXPECT_EQ("muchat/walter@dude", jid.Str()); + EXPECT_EQ("muchat", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_FALSE(jid.IsBare()); + EXPECT_TRUE(jid.IsFull()); +} diff --git a/webrtc/libjingle/xmpp/module.h b/webrtc/libjingle/xmpp/module.h new file mode 100644 index 0000000000..fa26df34c5 --- /dev/null +++ b/webrtc/libjingle/xmpp/module.h @@ -0,0 +1,35 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MODULE_H_ +#define WEBRTC_LIBJINGLE_XMPP_MODULE_H_ + +#include "webrtc/libjingle/xmpp/xmppengine.h" + +namespace buzz { + +class XmppEngine; + +//! This is the base class for extension modules. +//! An engine is registered with the module and the module then hooks the +//! appropriate parts of the engine to implement that set of features. It is +//! important to unregister modules before destructing the engine. +class XmppModule { +public: + virtual ~XmppModule() {} + + //! Register the engine with the module. Only one engine can be associated + //! with a module at a time. This method will return an error if there is + //! already an engine registered. + virtual XmppReturnStatus RegisterEngine(XmppEngine* engine) = 0; +}; + +} +#endif // WEBRTC_LIBJINGLE_XMPP_MODULE_H_ diff --git a/webrtc/libjingle/xmpp/moduleimpl.cc b/webrtc/libjingle/xmpp/moduleimpl.cc new file mode 100644 index 0000000000..b5337a642e --- /dev/null +++ b/webrtc/libjingle/xmpp/moduleimpl.cc @@ -0,0 +1,48 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/moduleimpl.h" +#include "webrtc/base/common.h" + +namespace buzz { + +XmppModuleImpl::XmppModuleImpl() : + engine_(NULL), + stanza_handler_(this) { +} + +XmppModuleImpl::~XmppModuleImpl() +{ + if (engine_ != NULL) { + engine_->RemoveStanzaHandler(&stanza_handler_); + engine_ = NULL; + } +} + +XmppReturnStatus +XmppModuleImpl::RegisterEngine(XmppEngine* engine) +{ + if (NULL == engine || NULL != engine_) + return XMPP_RETURN_BADARGUMENT; + + engine->AddStanzaHandler(&stanza_handler_); + engine_ = engine; + + return XMPP_RETURN_OK; +} + +XmppEngine* +XmppModuleImpl::engine() { + ASSERT(NULL != engine_); + return engine_; +} + +} + diff --git a/webrtc/libjingle/xmpp/moduleimpl.h b/webrtc/libjingle/xmpp/moduleimpl.h new file mode 100644 index 0000000000..5a7c6e360f --- /dev/null +++ b/webrtc/libjingle/xmpp/moduleimpl.h @@ -0,0 +1,76 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MODULEIMPL_H_ +#define WEBRTC_LIBJINGLE_XMPP_MODULEIMPL_H_ + +#include "webrtc/libjingle/xmpp/module.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" + +namespace buzz { + +//! This is the base implementation class for extension modules. +//! An engine is registered with the module and the module then hooks the +//! appropriate parts of the engine to implement that set of features. It is +//! important to unregister modules before destructing the engine. +class XmppModuleImpl { +protected: + XmppModuleImpl(); + virtual ~XmppModuleImpl(); + + //! Register the engine with the module. Only one engine can be associated + //! with a module at a time. This method will return an error if there is + //! already an engine registered. + XmppReturnStatus RegisterEngine(XmppEngine* engine); + + //! Gets the engine that this module is attached to. + XmppEngine* engine(); + + //! Process the given stanza. + //! The module must return true if it has handled the stanza. + //! A false return value causes the stanza to be passed on to + //! the next registered handler. + virtual bool HandleStanza(const XmlElement *) { return false; }; + +private: + + //! The ModuleSessionHelper nested class allows the Module + //! to hook into and get stanzas and events from the engine. + class ModuleStanzaHandler : public XmppStanzaHandler { + friend class XmppModuleImpl; + + ModuleStanzaHandler(XmppModuleImpl* module) : + module_(module) { + } + + bool HandleStanza(const XmlElement* stanza) { + return module_->HandleStanza(stanza); + } + + XmppModuleImpl* module_; + }; + + friend class ModuleStanzaHandler; + + XmppEngine* engine_; + ModuleStanzaHandler stanza_handler_; +}; + + +// This macro will implement the XmppModule interface for a class +// that derives from both XmppModuleImpl and XmppModule +#define IMPLEMENT_XMPPMODULE \ + XmppReturnStatus RegisterEngine(XmppEngine* engine) { \ + return XmppModuleImpl::RegisterEngine(engine); \ + } + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_MODULEIMPL_H_ diff --git a/webrtc/libjingle/xmpp/mucroomconfigtask.cc b/webrtc/libjingle/xmpp/mucroomconfigtask.cc new file mode 100644 index 0000000000..5938a2ca72 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomconfigtask.cc @@ -0,0 +1,73 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmpp/mucroomconfigtask.h" + +#include "webrtc/libjingle/xmpp/constants.h" + +namespace buzz { + +MucRoomConfigTask::MucRoomConfigTask( + XmppTaskParentInterface* parent, + const Jid& room_jid, + const std::string& room_name, + const std::vector& room_features) + : IqTask(parent, STR_SET, room_jid, + MakeRequest(room_name, room_features)), + room_jid_(room_jid) { +} + +XmlElement* MucRoomConfigTask::MakeRequest( + const std::string& room_name, + const std::vector& room_features) { + buzz::XmlElement* owner_query = new + buzz::XmlElement(buzz::QN_MUC_OWNER_QUERY, true); + + buzz::XmlElement* x_form = new buzz::XmlElement(buzz::QN_XDATA_X, true); + x_form->SetAttr(buzz::QN_TYPE, buzz::STR_FORM); + + buzz::XmlElement* roomname_field = + new buzz::XmlElement(buzz::QN_XDATA_FIELD, false); + roomname_field->SetAttr(buzz::QN_VAR, buzz::STR_MUC_ROOMCONFIG_ROOMNAME); + roomname_field->SetAttr(buzz::QN_TYPE, buzz::STR_TEXT_SINGLE); + + buzz::XmlElement* roomname_value = + new buzz::XmlElement(buzz::QN_XDATA_VALUE, false); + roomname_value->SetBodyText(room_name); + + roomname_field->AddElement(roomname_value); + x_form->AddElement(roomname_field); + + buzz::XmlElement* features_field = + new buzz::XmlElement(buzz::QN_XDATA_FIELD, false); + features_field->SetAttr(buzz::QN_VAR, buzz::STR_MUC_ROOMCONFIG_FEATURES); + features_field->SetAttr(buzz::QN_TYPE, buzz::STR_LIST_MULTI); + + for (std::vector::const_iterator feature = room_features.begin(); + feature != room_features.end(); ++feature) { + buzz::XmlElement* features_value = + new buzz::XmlElement(buzz::QN_XDATA_VALUE, false); + features_value->SetBodyText(*feature); + features_field->AddElement(features_value); + } + + x_form->AddElement(features_field); + owner_query->AddElement(x_form); + return owner_query; +} + +void MucRoomConfigTask::HandleResult(const XmlElement* element) { + SignalResult(this); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/mucroomconfigtask.h b/webrtc/libjingle/xmpp/mucroomconfigtask.h new file mode 100644 index 0000000000..d297d027f2 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomconfigtask.h @@ -0,0 +1,47 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMCONFIGTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_MUCROOMCONFIGTASK_H_ + +#include +#include "webrtc/libjingle/xmpp/iqtask.h" + +namespace buzz { + +// This task configures the muc room for document sharing and other enterprise +// specific goodies. +class MucRoomConfigTask : public IqTask { + public: + MucRoomConfigTask(XmppTaskParentInterface* parent, + const Jid& room_jid, + const std::string& room_name, + const std::vector& room_features); + + // Room configuration does not return any reasonable error + // values. The First config request configures the room, subseqent + // ones are just ignored by server and server returns empty + // response. + sigslot::signal1 SignalResult; + + const Jid& room_jid() const { return room_jid_; } + + protected: + virtual void HandleResult(const XmlElement* stanza); + + private: + static XmlElement* MakeRequest(const std::string& room_name, + const std::vector& room_features); + Jid room_jid_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMCONFIGTASK_H_ diff --git a/webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc b/webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc new file mode 100644 index 0000000000..a86dd14f59 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc @@ -0,0 +1,127 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomconfigtask.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class MucRoomConfigListener : public sigslot::has_slots<> { + public: + MucRoomConfigListener() : result_count(0), error_count(0) {} + + void OnResult(buzz::MucRoomConfigTask*) { + ++result_count; + } + + void OnError(buzz::IqTask* task, + const buzz::XmlElement* error) { + ++error_count; + } + + int result_count; + int error_count; +}; + +class MucRoomConfigTaskTest : public testing::Test { + public: + MucRoomConfigTaskTest() : + room_jid("muc-jid-ponies@domain.com"), + room_name("ponies") { + } + + virtual void SetUp() { + runner = new rtc::FakeTaskRunner(); + xmpp_client = new buzz::FakeXmppClient(runner); + listener = new MucRoomConfigListener(); + } + + virtual void TearDown() { + delete listener; + // delete xmpp_client; Deleted by deleting runner. + delete runner; + } + + rtc::FakeTaskRunner* runner; + buzz::FakeXmppClient* xmpp_client; + MucRoomConfigListener* listener; + buzz::Jid room_jid; + std::string room_name; +}; + +TEST_F(MucRoomConfigTaskTest, TestConfigEnterprise) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + std::vector room_features; + room_features.push_back("feature1"); + room_features.push_back("feature2"); + buzz::MucRoomConfigTask* task = new buzz::MucRoomConfigTask( + xmpp_client, room_jid, "ponies", room_features); + EXPECT_EQ(room_jid, task->room_jid()); + + task->SignalResult.connect(listener, &MucRoomConfigListener::OnResult); + task->Start(); + + std::string expected_iq = + "" + "" + "" + "" + "ponies" + "" + "" + "feature1" + "feature2" + "" + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + EXPECT_EQ(0, listener->result_count); + EXPECT_EQ(0, listener->error_count); + + std::string response_iq = + "" + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + + EXPECT_EQ(1, listener->result_count); + EXPECT_EQ(0, listener->error_count); +} + +TEST_F(MucRoomConfigTaskTest, TestError) { + std::vector room_features; + buzz::MucRoomConfigTask* task = new buzz::MucRoomConfigTask( + xmpp_client, room_jid, "ponies", room_features); + task->SignalError.connect(listener, &MucRoomConfigListener::OnError); + task->Start(); + + std::string error_iq = + "" + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq)); + + EXPECT_EQ(0, listener->result_count); + EXPECT_EQ(1, listener->error_count); +} diff --git a/webrtc/libjingle/xmpp/mucroomdiscoverytask.cc b/webrtc/libjingle/xmpp/mucroomdiscoverytask.cc new file mode 100644 index 0000000000..05a56716b9 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomdiscoverytask.cc @@ -0,0 +1,66 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/mucroomdiscoverytask.h" + +#include "webrtc/libjingle/xmpp/constants.h" + +namespace buzz { + +MucRoomDiscoveryTask::MucRoomDiscoveryTask( + XmppTaskParentInterface* parent, + const Jid& room_jid) + : IqTask(parent, STR_GET, room_jid, + new buzz::XmlElement(buzz::QN_DISCO_INFO_QUERY)) { +} + +void MucRoomDiscoveryTask::HandleResult(const XmlElement* stanza) { + const XmlElement* query = stanza->FirstNamed(QN_DISCO_INFO_QUERY); + if (query == NULL) { + SignalError(this, NULL); + return; + } + + std::set features; + std::map extended_info; + const XmlElement* identity = query->FirstNamed(QN_DISCO_IDENTITY); + if (identity == NULL || !identity->HasAttr(QN_NAME)) { + SignalResult(this, false, "", "", features, extended_info); + return; + } + + const std::string name(identity->Attr(QN_NAME)); + + // Get the conversation id + const XmlElement* conversation = + identity->FirstNamed(QN_GOOGLE_MUC_HANGOUT_CONVERSATION_ID); + std::string conversation_id; + if (conversation != NULL) { + conversation_id = conversation->BodyText(); + } + + for (const XmlElement* feature = query->FirstNamed(QN_DISCO_FEATURE); + feature != NULL; feature = feature->NextNamed(QN_DISCO_FEATURE)) { + features.insert(feature->Attr(QN_VAR)); + } + + const XmlElement* data_x = query->FirstNamed(QN_XDATA_X); + if (data_x != NULL) { + for (const XmlElement* field = data_x->FirstNamed(QN_XDATA_FIELD); + field != NULL; field = field->NextNamed(QN_XDATA_FIELD)) { + const std::string key(field->Attr(QN_VAR)); + extended_info[key] = field->Attr(QN_XDATA_VALUE); + } + } + + SignalResult(this, true, name, conversation_id, features, extended_info); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/mucroomdiscoverytask.h b/webrtc/libjingle/xmpp/mucroomdiscoverytask.h new file mode 100644 index 0000000000..3e332bd297 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomdiscoverytask.h @@ -0,0 +1,41 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMDISCOVERYTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_MUCROOMDISCOVERYTASK_H_ + +#include +#include +#include "webrtc/libjingle/xmpp/iqtask.h" + +namespace buzz { + +// This task requests the feature capabilities of the room. It is based on +// XEP-0030, and extended using XEP-0004. +class MucRoomDiscoveryTask : public IqTask { + public: + MucRoomDiscoveryTask(XmppTaskParentInterface* parent, + const Jid& room_jid); + + // Signal (exists, name, conversationId, features, extended_info) + sigslot::signal6&, + const std::map& > SignalResult; + + protected: + virtual void HandleResult(const XmlElement* stanza); +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMDISCOVERYTASK_H_ diff --git a/webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc b/webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc new file mode 100644 index 0000000000..cdb50c21b9 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc @@ -0,0 +1,145 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomdiscoverytask.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class MucRoomDiscoveryListener : public sigslot::has_slots<> { + public: + MucRoomDiscoveryListener() : error_count(0) {} + + void OnResult(buzz::MucRoomDiscoveryTask* task, + bool exists, + const std::string& name, + const std::string& conversation_id, + const std::set& features, + const std::map& extended_info) { + last_exists = exists; + last_name = name; + last_conversation_id = conversation_id; + last_features = features; + last_extended_info = extended_info; + } + + void OnError(buzz::IqTask* task, + const buzz::XmlElement* error) { + ++error_count; + } + + bool last_exists; + std::string last_name; + std::string last_conversation_id; + std::set last_features; + std::map last_extended_info; + int error_count; +}; + +class MucRoomDiscoveryTaskTest : public testing::Test { + public: + MucRoomDiscoveryTaskTest() : + room_jid("muc-jid-ponies@domain.com"), + room_name("ponies"), + conversation_id("test_conversation_id") { + } + + virtual void SetUp() { + runner = new rtc::FakeTaskRunner(); + xmpp_client = new buzz::FakeXmppClient(runner); + listener = new MucRoomDiscoveryListener(); + } + + virtual void TearDown() { + delete listener; + // delete xmpp_client; Deleted by deleting runner. + delete runner; + } + + rtc::FakeTaskRunner* runner; + buzz::FakeXmppClient* xmpp_client; + MucRoomDiscoveryListener* listener; + buzz::Jid room_jid; + std::string room_name; + std::string conversation_id; +}; + +TEST_F(MucRoomDiscoveryTaskTest, TestDiscovery) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + buzz::MucRoomDiscoveryTask* task = new buzz::MucRoomDiscoveryTask( + xmpp_client, room_jid); + task->SignalResult.connect(listener, &MucRoomDiscoveryListener::OnResult); + task->Start(); + + std::string expected_iq = + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + EXPECT_EQ("", listener->last_name); + EXPECT_EQ("", listener->last_conversation_id); + + std::string response_iq = + "" + " " + " " + " " + "test_conversation_id" + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + + EXPECT_EQ(true, listener->last_exists); + EXPECT_EQ(room_name, listener->last_name); + EXPECT_EQ(conversation_id, listener->last_conversation_id); + EXPECT_EQ(2U, listener->last_features.size()); + EXPECT_EQ(1U, listener->last_features.count("feature1")); + EXPECT_EQ(2U, listener->last_extended_info.size()); + EXPECT_EQ("value1", listener->last_extended_info["var1"]); + EXPECT_EQ(0, listener->error_count); +} + +TEST_F(MucRoomDiscoveryTaskTest, TestMissingName) { + buzz::MucRoomDiscoveryTask* task = new buzz::MucRoomDiscoveryTask( + xmpp_client, room_jid); + task->SignalError.connect(listener, &MucRoomDiscoveryListener::OnError); + task->Start(); + + std::string error_iq = + "" + " " + " " + " " + ""; + EXPECT_EQ(0, listener->error_count); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq)); + EXPECT_EQ(0, listener->error_count); +} diff --git a/webrtc/libjingle/xmpp/mucroomlookuptask.cc b/webrtc/libjingle/xmpp/mucroomlookuptask.cc new file mode 100644 index 0000000000..a52491c918 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomlookuptask.cc @@ -0,0 +1,158 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/mucroomlookuptask.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/logging.h" + + +namespace buzz { + +MucRoomLookupTask* +MucRoomLookupTask::CreateLookupTaskForRoomName(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& room_name, + const std::string& room_domain) { + return new MucRoomLookupTask(parent, lookup_server_jid, + MakeNameQuery(room_name, room_domain)); +} + +MucRoomLookupTask* +MucRoomLookupTask::CreateLookupTaskForRoomJid(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const Jid& room_jid) { + return new MucRoomLookupTask(parent, lookup_server_jid, + MakeJidQuery(room_jid)); +} + +MucRoomLookupTask* +MucRoomLookupTask::CreateLookupTaskForHangoutId(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& hangout_id) { + return new MucRoomLookupTask(parent, lookup_server_jid, + MakeHangoutIdQuery(hangout_id)); +} + +MucRoomLookupTask* +MucRoomLookupTask::CreateLookupTaskForExternalId( + XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& external_id, + const std::string& type) { + return new MucRoomLookupTask(parent, lookup_server_jid, + MakeExternalIdQuery(external_id, type)); +} + +MucRoomLookupTask::MucRoomLookupTask(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + XmlElement* query) + : IqTask(parent, STR_SET, lookup_server_jid, query) { +} + +XmlElement* MucRoomLookupTask::MakeNameQuery( + const std::string& room_name, const std::string& room_domain) { + XmlElement* name_elem = new XmlElement(QN_SEARCH_ROOM_NAME, false); + name_elem->SetBodyText(room_name); + + XmlElement* domain_elem = new XmlElement(QN_SEARCH_ROOM_DOMAIN, false); + domain_elem->SetBodyText(room_domain); + + XmlElement* query = new XmlElement(QN_SEARCH_QUERY, true); + query->AddElement(name_elem); + query->AddElement(domain_elem); + return query; +} + +XmlElement* MucRoomLookupTask::MakeJidQuery(const Jid& room_jid) { + XmlElement* jid_elem = new XmlElement(QN_SEARCH_ROOM_JID); + jid_elem->SetBodyText(room_jid.Str()); + + XmlElement* query = new XmlElement(QN_SEARCH_QUERY); + query->AddElement(jid_elem); + return query; +} + +XmlElement* MucRoomLookupTask::MakeExternalIdQuery( + const std::string& external_id, const std::string& type) { + XmlElement* external_id_elem = new XmlElement(QN_SEARCH_EXTERNAL_ID); + external_id_elem->SetAttr(QN_TYPE, type); + external_id_elem->SetBodyText(external_id); + + XmlElement* query = new XmlElement(QN_SEARCH_QUERY); + query->AddElement(external_id_elem); + return query; +} + +// Construct a stanza to lookup the muc jid for a given hangout id. eg: +// +// +// 0b48ad092c893a53b7bfc87422caf38e93978798e +// +XmlElement* MucRoomLookupTask::MakeHangoutIdQuery( + const std::string& hangout_id) { + XmlElement* hangout_id_elem = new XmlElement(QN_SEARCH_HANGOUT_ID, false); + hangout_id_elem->SetBodyText(hangout_id); + + XmlElement* query = new XmlElement(QN_SEARCH_QUERY, true); + query->AddElement(hangout_id_elem); + return query; +} + +// Handle a response like the following: +// +// +// +// 0b48ad092c893a53b7bfc87422caf38e93978798e +// hangout.google.com +// +// +void MucRoomLookupTask::HandleResult(const XmlElement* stanza) { + const XmlElement* query_elem = stanza->FirstNamed(QN_SEARCH_QUERY); + if (query_elem == NULL) { + SignalError(this, stanza); + return; + } + + const XmlElement* item_elem = query_elem->FirstNamed(QN_SEARCH_ITEM); + if (item_elem == NULL) { + SignalError(this, stanza); + return; + } + + MucRoomInfo room; + room.jid = Jid(item_elem->Attr(buzz::QN_JID)); + if (!room.jid.IsValid()) { + SignalError(this, stanza); + return; + } + + const XmlElement* room_name_elem = + item_elem->FirstNamed(QN_SEARCH_ROOM_NAME); + if (room_name_elem != NULL) { + room.name = room_name_elem->BodyText(); + } + + const XmlElement* room_domain_elem = + item_elem->FirstNamed(QN_SEARCH_ROOM_DOMAIN); + if (room_domain_elem != NULL) { + room.domain = room_domain_elem->BodyText(); + } + + const XmlElement* hangout_id_elem = + item_elem->FirstNamed(QN_SEARCH_HANGOUT_ID); + if (hangout_id_elem != NULL) { + room.hangout_id = hangout_id_elem->BodyText(); + } + + SignalResult(this, room); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/mucroomlookuptask.h b/webrtc/libjingle/xmpp/mucroomlookuptask.h new file mode 100644 index 0000000000..d87b3da20b --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomlookuptask.h @@ -0,0 +1,76 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMLOOKUPTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_MUCROOMLOOKUPTASK_H_ + +#include +#include "webrtc/libjingle/xmpp/iqtask.h" + +namespace buzz { + +struct MucRoomInfo { + Jid jid; + std::string name; + std::string domain; + std::string hangout_id; + + std::string full_name() const { + return name + "@" + domain; + } +}; + +class MucRoomLookupTask : public IqTask { + public: + enum IdType { + ID_TYPE_CONVERSATION, + ID_TYPE_HANGOUT + }; + + static MucRoomLookupTask* + CreateLookupTaskForRoomName(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& room_name, + const std::string& room_domain); + static MucRoomLookupTask* + CreateLookupTaskForRoomJid(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const Jid& room_jid); + static MucRoomLookupTask* + CreateLookupTaskForHangoutId(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& hangout_id); + static MucRoomLookupTask* + CreateLookupTaskForExternalId(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& external_id, + const std::string& type); + + sigslot::signal2 SignalResult; + + protected: + virtual void HandleResult(const XmlElement* element); + + private: + MucRoomLookupTask(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + XmlElement* query); + static XmlElement* MakeNameQuery(const std::string& room_name, + const std::string& room_domain); + static XmlElement* MakeJidQuery(const Jid& room_jid); + static XmlElement* MakeHangoutIdQuery(const std::string& hangout_id); + static XmlElement* MakeExternalIdQuery(const std::string& external_id, + const std::string& type); +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMLOOKUPTASK_H_ diff --git a/webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc b/webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc new file mode 100644 index 0000000000..da5b1458e5 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc @@ -0,0 +1,187 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomlookuptask.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class MucRoomLookupListener : public sigslot::has_slots<> { + public: + MucRoomLookupListener() : error_count(0) {} + + void OnResult(buzz::MucRoomLookupTask* task, + const buzz::MucRoomInfo& room) { + last_room = room; + } + + void OnError(buzz::IqTask* task, + const buzz::XmlElement* error) { + ++error_count; + } + + buzz::MucRoomInfo last_room; + int error_count; +}; + +class MucRoomLookupTaskTest : public testing::Test { + public: + MucRoomLookupTaskTest() : + lookup_server_jid("lookup@domain.com"), + room_jid("muc-jid-ponies@domain.com"), + room_name("ponies"), + room_domain("domain.com"), + room_full_name("ponies@domain.com"), + hangout_id("some_hangout_id") { + } + + virtual void SetUp() { + runner = new rtc::FakeTaskRunner(); + xmpp_client = new buzz::FakeXmppClient(runner); + listener = new MucRoomLookupListener(); + } + + virtual void TearDown() { + delete listener; + // delete xmpp_client; Deleted by deleting runner. + delete runner; + } + + rtc::FakeTaskRunner* runner; + buzz::FakeXmppClient* xmpp_client; + MucRoomLookupListener* listener; + buzz::Jid lookup_server_jid; + buzz::Jid room_jid; + std::string room_name; + std::string room_domain; + std::string room_full_name; + std::string hangout_id; +}; + +TEST_F(MucRoomLookupTaskTest, TestLookupName) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + buzz::MucRoomLookupTask* task = + buzz::MucRoomLookupTask::CreateLookupTaskForRoomName( + xmpp_client, lookup_server_jid, room_name, room_domain); + task->SignalResult.connect(listener, &MucRoomLookupListener::OnResult); + task->Start(); + + std::string expected_iq = + "" + "" + "ponies" + "domain.com" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + EXPECT_EQ("", listener->last_room.name); + + std::string response_iq = + "" + " " + " " + " ponies" + " domain.com" + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + + EXPECT_EQ(room_name, listener->last_room.name); + EXPECT_EQ(room_domain, listener->last_room.domain); + EXPECT_EQ(room_jid, listener->last_room.jid); + EXPECT_EQ(room_full_name, listener->last_room.full_name()); + EXPECT_EQ(0, listener->error_count); +} + +TEST_F(MucRoomLookupTaskTest, TestLookupHangoutId) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForHangoutId( + xmpp_client, lookup_server_jid, hangout_id); + task->SignalResult.connect(listener, &MucRoomLookupListener::OnResult); + task->Start(); + + std::string expected_iq = + "" + "" + "some_hangout_id" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + EXPECT_EQ("", listener->last_room.name); + + std::string response_iq = + "" + " " + " " + " some_hangout_id" + " domain.com" + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + + EXPECT_EQ(hangout_id, listener->last_room.name); + EXPECT_EQ(room_domain, listener->last_room.domain); + EXPECT_EQ(room_jid, listener->last_room.jid); + EXPECT_EQ(0, listener->error_count); +} + +TEST_F(MucRoomLookupTaskTest, TestError) { + buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForRoomName( + xmpp_client, lookup_server_jid, room_name, room_domain); + task->SignalError.connect(listener, &MucRoomLookupListener::OnError); + task->Start(); + + std::string error_iq = + "" + ""; + + EXPECT_EQ(0, listener->error_count); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq)); + EXPECT_EQ(1, listener->error_count); +} + +TEST_F(MucRoomLookupTaskTest, TestBadJid) { + buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForRoomName( + xmpp_client, lookup_server_jid, room_name, room_domain); + task->SignalError.connect(listener, &MucRoomLookupListener::OnError); + task->Start(); + + std::string response_iq = + "" + " " + " " + " " + ""; + + EXPECT_EQ(0, listener->error_count); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + EXPECT_EQ(1, listener->error_count); +} diff --git a/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc new file mode 100644 index 0000000000..79ccc29f55 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc @@ -0,0 +1,51 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h" + +#include "webrtc/libjingle/xmpp/constants.h" + +namespace buzz { + +MucRoomUniqueHangoutIdTask::MucRoomUniqueHangoutIdTask(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid) + : IqTask(parent, STR_GET, lookup_server_jid, MakeUniqueRequestXml()) { +} + +// Construct a stanza to request a unique room id. eg: +// +// +XmlElement* MucRoomUniqueHangoutIdTask::MakeUniqueRequestXml() { + XmlElement* xml = new XmlElement(QN_MUC_UNIQUE_QUERY, false); + xml->SetAttr(QN_HANGOUT_ID, STR_TRUE); + return xml; +} + +// Handle a response like the following: +// +// +// muvc-private-chat-guid@groupchat.google.com +// +void MucRoomUniqueHangoutIdTask::HandleResult(const XmlElement* stanza) { + + const XmlElement* unique_elem = stanza->FirstNamed(QN_MUC_UNIQUE_QUERY); + if (unique_elem == NULL || + !unique_elem->HasAttr(QN_HANGOUT_ID)) { + SignalError(this, stanza); + return; + } + + std::string hangout_id = unique_elem->Attr(QN_HANGOUT_ID); + + SignalResult(this, hangout_id); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h new file mode 100644 index 0000000000..ac662503f1 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h @@ -0,0 +1,38 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_ + +#include "webrtc/libjingle/xmpp/iqtask.h" + +namespace buzz { + +// Task to request a unique hangout id to be used when starting a hangout. +// The protocol is described in https://docs.google.com/a/google.com/ +// document/d/1EFLT6rCYPDVdqQXSQliXwqB3iUkpZJ9B_MNFeOZgN7g/edit +class MucRoomUniqueHangoutIdTask : public buzz::IqTask { + public: + MucRoomUniqueHangoutIdTask(buzz::XmppTaskParentInterface* parent, + const Jid& lookup_server_jid); + // signal(task, hangout_id) + sigslot::signal2 SignalResult; + + protected: + virtual void HandleResult(const buzz::XmlElement* stanza); + + private: + static buzz::XmlElement* MakeUniqueRequestXml(); + +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_ diff --git a/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc new file mode 100644 index 0000000000..2480528b41 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc @@ -0,0 +1,99 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class MucRoomUniqueHangoutIdListener : public sigslot::has_slots<> { + public: + MucRoomUniqueHangoutIdListener() : error_count(0) {} + + void OnResult(buzz::MucRoomUniqueHangoutIdTask* task, + const std::string& hangout_id) { + last_hangout_id = hangout_id; + } + + void OnError(buzz::IqTask* task, + const buzz::XmlElement* error) { + ++error_count; + } + + std::string last_hangout_id; + int error_count; +}; + +class MucRoomUniqueHangoutIdTaskTest : public testing::Test { + public: + MucRoomUniqueHangoutIdTaskTest() : + lookup_server_jid("lookup@domain.com"), + hangout_id("some_hangout_id") { + } + + virtual void SetUp() { + runner = new rtc::FakeTaskRunner(); + xmpp_client = new buzz::FakeXmppClient(runner); + listener = new MucRoomUniqueHangoutIdListener(); + } + + virtual void TearDown() { + delete listener; + // delete xmpp_client; Deleted by deleting runner. + delete runner; + } + + rtc::FakeTaskRunner* runner; + buzz::FakeXmppClient* xmpp_client; + MucRoomUniqueHangoutIdListener* listener; + buzz::Jid lookup_server_jid; + std::string hangout_id; +}; + +TEST_F(MucRoomUniqueHangoutIdTaskTest, Test) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + buzz::MucRoomUniqueHangoutIdTask* task = new buzz::MucRoomUniqueHangoutIdTask( + xmpp_client, lookup_server_jid); + task->SignalResult.connect(listener, &MucRoomUniqueHangoutIdListener::OnResult); + task->Start(); + + std::string expected_iq = + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + EXPECT_EQ("", listener->last_hangout_id); + + std::string response_iq = + "" + "" + "muvc-private-chat-00001234-5678-9abc-def0-123456789abc" + "" + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + + EXPECT_EQ(hangout_id, listener->last_hangout_id); + EXPECT_EQ(0, listener->error_count); +} + diff --git a/webrtc/libjingle/xmpp/pingtask.cc b/webrtc/libjingle/xmpp/pingtask.cc new file mode 100644 index 0000000000..f7bef11043 --- /dev/null +++ b/webrtc/libjingle/xmpp/pingtask.cc @@ -0,0 +1,93 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/pingtask.h" + +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/logging.h" + +namespace buzz { + +PingTask::PingTask(buzz::XmppTaskParentInterface* parent, + rtc::MessageQueue* message_queue, + uint32_t ping_period_millis, + uint32_t ping_timeout_millis) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE), + message_queue_(message_queue), + ping_period_millis_(ping_period_millis), + ping_timeout_millis_(ping_timeout_millis), + next_ping_time_(0), + ping_response_deadline_(0) { + ASSERT(ping_period_millis >= ping_timeout_millis); +} + +bool PingTask::HandleStanza(const buzz::XmlElement* stanza) { + if (!MatchResponseIq(stanza, Jid(STR_EMPTY), task_id())) { + return false; + } + + if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_RESULT && + stanza->Attr(buzz::QN_TYPE) != buzz::STR_ERROR) { + return false; + } + + QueueStanza(stanza); + return true; +} + +// This task runs indefinitely and remains in either the start or blocked +// states. +int PingTask::ProcessStart() { + if (ping_period_millis_ < ping_timeout_millis_) { + LOG(LS_ERROR) << "ping_period_millis should be >= ping_timeout_millis"; + return STATE_ERROR; + } + const buzz::XmlElement* stanza = NextStanza(); + if (stanza != NULL) { + // Received a ping response of some sort (don't care what it is). + ping_response_deadline_ = 0; + } + + int64_t now = rtc::TimeMillis(); + + // If the ping timed out, signal. + if (ping_response_deadline_ != 0 && now >= ping_response_deadline_) { + SignalTimeout(); + return STATE_ERROR; + } + + // Send a ping if it's time. + if (now >= next_ping_time_) { + std::unique_ptr stanza( + MakeIq(buzz::STR_GET, Jid(STR_EMPTY), task_id())); + stanza->AddElement(new buzz::XmlElement(QN_PING)); + SendStanza(stanza.get()); + + ping_response_deadline_ = now + ping_timeout_millis_; + next_ping_time_ = now + ping_period_millis_; + + // Wake ourselves up when it's time to send another ping or when the ping + // times out (so we can fire a signal). + message_queue_->PostDelayed(RTC_FROM_HERE, ping_timeout_millis_, this); + message_queue_->PostDelayed(RTC_FROM_HERE, ping_period_millis_, this); + } + + return STATE_BLOCKED; +} + +void PingTask::OnMessage(rtc::Message* msg) { + // Get the task manager to run this task so we can send a ping or signal or + // process a ping response. + Wake(); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/pingtask.h b/webrtc/libjingle/xmpp/pingtask.h new file mode 100644 index 0000000000..b070a1ccf1 --- /dev/null +++ b/webrtc/libjingle/xmpp/pingtask.h @@ -0,0 +1,55 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PINGTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_PINGTASK_H_ + +#include "webrtc/libjingle/xmpp/xmpptask.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/messagequeue.h" + +namespace buzz { + +// Task to periodically send pings to the server to ensure that the network +// connection is valid, implementing XEP-0199. +// +// This is especially useful on cellular networks because: +// 1. It keeps the connections alive through the cellular network's NATs or +// proxies. +// 2. It detects when the server has crashed or any other case in which the +// connection has broken without a fin or reset packet being sent to us. +class PingTask : public buzz::XmppTask, private rtc::MessageHandler { + public: + PingTask(buzz::XmppTaskParentInterface* parent, + rtc::MessageQueue* message_queue, + uint32_t ping_period_millis, + uint32_t ping_timeout_millis); + + virtual bool HandleStanza(const buzz::XmlElement* stanza); + virtual int ProcessStart(); + + // Raised if there is no response to a ping within ping_timeout_millis. + // The task is automatically aborted after a timeout. + sigslot::signal0<> SignalTimeout; + + private: + // Implementation of MessageHandler. + virtual void OnMessage(rtc::Message* msg); + + rtc::MessageQueue* message_queue_; + uint32_t ping_period_millis_; + uint32_t ping_timeout_millis_; + int64_t next_ping_time_; + int64_t ping_response_deadline_; // 0 if the response has been received +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_PINGTASK_H_ diff --git a/webrtc/libjingle/xmpp/pingtask_unittest.cc b/webrtc/libjingle/xmpp/pingtask_unittest.cc new file mode 100644 index 0000000000..b9aab6b3f2 --- /dev/null +++ b/webrtc/libjingle/xmpp/pingtask_unittest.cc @@ -0,0 +1,101 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/pingtask.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class PingTaskTest; + +class PingXmppClient : public buzz::FakeXmppClient { + public: + PingXmppClient(rtc::TaskParent* parent, PingTaskTest* tst) : + FakeXmppClient(parent), test(tst) { + } + + buzz::XmppReturnStatus SendStanza(const buzz::XmlElement* stanza); + + private: + PingTaskTest* test; +}; + +class PingTaskTest : public testing::Test, public sigslot::has_slots<> { + public: + PingTaskTest() : respond_to_pings(true), timed_out(false) { + } + + virtual void SetUp() { + runner = new rtc::FakeTaskRunner(); + xmpp_client = new PingXmppClient(runner, this); + } + + virtual void TearDown() { + // delete xmpp_client; Deleted by deleting runner. + delete runner; + } + + void ConnectTimeoutSignal(buzz::PingTask* task) { + task->SignalTimeout.connect(this, &PingTaskTest::OnPingTimeout); + } + + void OnPingTimeout() { + timed_out = true; + } + + rtc::FakeTaskRunner* runner; + PingXmppClient* xmpp_client; + bool respond_to_pings; + bool timed_out; +}; + +buzz::XmppReturnStatus PingXmppClient::SendStanza( + const buzz::XmlElement* stanza) { + buzz::XmppReturnStatus result = FakeXmppClient::SendStanza(stanza); + if (test->respond_to_pings && (stanza->FirstNamed(buzz::QN_PING) != NULL)) { + std::string ping_response = + ""; + HandleStanza(buzz::XmlElement::ForStr(ping_response)); + } + return result; +} + +TEST_F(PingTaskTest, TestSuccess) { + uint32_t ping_period_millis = 100; + buzz::PingTask* task = new buzz::PingTask(xmpp_client, + rtc::Thread::Current(), + ping_period_millis, ping_period_millis / 10); + ConnectTimeoutSignal(task); + task->Start(); + unsigned int expected_ping_count = 5U; + EXPECT_EQ_WAIT(xmpp_client->sent_stanzas().size(), expected_ping_count, + ping_period_millis * (expected_ping_count + 1)); + EXPECT_FALSE(task->IsDone()); + EXPECT_FALSE(timed_out); +} + +TEST_F(PingTaskTest, TestTimeout) { + respond_to_pings = false; + uint32_t ping_timeout_millis = 200; + buzz::PingTask* task = new buzz::PingTask(xmpp_client, + rtc::Thread::Current(), + ping_timeout_millis * 10, ping_timeout_millis); + ConnectTimeoutSignal(task); + task->Start(); + WAIT(false, ping_timeout_millis / 2); + EXPECT_FALSE(timed_out); + EXPECT_TRUE_WAIT(timed_out, ping_timeout_millis * 2); +} diff --git a/webrtc/libjingle/xmpp/plainsaslhandler.h b/webrtc/libjingle/xmpp/plainsaslhandler.h new file mode 100644 index 0000000000..aa6a791ebc --- /dev/null +++ b/webrtc/libjingle/xmpp/plainsaslhandler.h @@ -0,0 +1,64 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PLAINSASLHANDLER_H_ +#define WEBRTC_LIBJINGLE_XMPP_PLAINSASLHANDLER_H_ + +#include +#include "webrtc/libjingle/xmpp/saslhandler.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" +#include "webrtc/base/cryptstring.h" + +namespace buzz { + +class PlainSaslHandler : public SaslHandler { +public: + PlainSaslHandler(const Jid & jid, const rtc::CryptString & password, + bool allow_plain) : jid_(jid), password_(password), + allow_plain_(allow_plain) {} + + virtual ~PlainSaslHandler() {} + + // Should pick the best method according to this handler + // returns the empty string if none are suitable + virtual std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted) { + + if (!encrypted && !allow_plain_) { + return ""; + } + + std::vector::const_iterator it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN"); + if (it == mechanisms.end()) { + return ""; + } + else { + return "PLAIN"; + } + } + + // Creates a SaslMechanism for the given mechanism name (you own it + // once you get it). If not handled, return NULL. + virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) { + if (mechanism == "PLAIN") { + return new SaslPlainMechanism(jid_, password_); + } + return NULL; + } + +private: + Jid jid_; + rtc::CryptString password_; + bool allow_plain_; +}; + + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_PLAINSASLHANDLER_H_ diff --git a/webrtc/libjingle/xmpp/presenceouttask.cc b/webrtc/libjingle/xmpp/presenceouttask.cc new file mode 100644 index 0000000000..5519a4fd4a --- /dev/null +++ b/webrtc/libjingle/xmpp/presenceouttask.cc @@ -0,0 +1,141 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/presenceouttask.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/base/arraysize.h" +#include "webrtc/base/stringencode.h" + +namespace buzz { + +XmppReturnStatus +PresenceOutTask::Send(const PresenceStatus & s) { + if (GetState() != STATE_INIT && GetState() != STATE_START) + return XMPP_RETURN_BADSTATE; + + XmlElement * presence = TranslateStatus(s); + QueueStanza(presence); + delete presence; + return XMPP_RETURN_OK; +} + +XmppReturnStatus +PresenceOutTask::SendDirected(const Jid & j, const PresenceStatus & s) { + if (GetState() != STATE_INIT && GetState() != STATE_START) + return XMPP_RETURN_BADSTATE; + + XmlElement * presence = TranslateStatus(s); + presence->AddAttr(QN_TO, j.Str()); + QueueStanza(presence); + delete presence; + return XMPP_RETURN_OK; +} + +XmppReturnStatus PresenceOutTask::SendProbe(const Jid & jid) { + if (GetState() != STATE_INIT && GetState() != STATE_START) + return XMPP_RETURN_BADSTATE; + + XmlElement * presence = new XmlElement(QN_PRESENCE); + presence->AddAttr(QN_TO, jid.Str()); + presence->AddAttr(QN_TYPE, "probe"); + + QueueStanza(presence); + delete presence; + return XMPP_RETURN_OK; +} + +int +PresenceOutTask::ProcessStart() { + const XmlElement * stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + + if (SendStanza(stanza) != XMPP_RETURN_OK) + return STATE_ERROR; + + return STATE_START; +} + +XmlElement * +PresenceOutTask::TranslateStatus(const PresenceStatus & s) { + XmlElement * result = new XmlElement(QN_PRESENCE); + if (!s.available()) { + result->AddAttr(QN_TYPE, STR_UNAVAILABLE); + } + else { + if (s.show() != PresenceStatus::SHOW_ONLINE && + s.show() != PresenceStatus::SHOW_OFFLINE) { + result->AddElement(new XmlElement(QN_SHOW)); + switch (s.show()) { + default: + result->AddText(STR_SHOW_AWAY, 1); + break; + case PresenceStatus::SHOW_XA: + result->AddText(STR_SHOW_XA, 1); + break; + case PresenceStatus::SHOW_DND: + result->AddText(STR_SHOW_DND, 1); + break; + case PresenceStatus::SHOW_CHAT: + result->AddText(STR_SHOW_CHAT, 1); + break; + } + } + + result->AddElement(new XmlElement(QN_STATUS)); + result->AddText(s.status(), 1); + + if (!s.nick().empty()) { + result->AddElement(new XmlElement(QN_NICKNAME)); + result->AddText(s.nick(), 1); + } + + std::string pri; + rtc::ToString(s.priority(), &pri); + + result->AddElement(new XmlElement(QN_PRIORITY)); + result->AddText(pri, 1); + + if (s.know_capabilities()) { + result->AddElement(new XmlElement(QN_CAPS_C, true)); + result->AddAttr(QN_NODE, s.caps_node(), 1); + result->AddAttr(QN_VER, s.version(), 1); + + std::string caps; + caps.append(s.voice_capability() ? "voice-v1" : ""); + caps.append(s.pmuc_capability() ? " pmuc-v1" : ""); + caps.append(s.video_capability() ? " video-v1" : ""); + caps.append(s.camera_capability() ? " camera-v1" : ""); + + result->AddAttr(QN_EXT, caps, 1); + } + + // Put the delay mark on the presence according to JEP-0091 + { + result->AddElement(new XmlElement(kQnDelayX, true)); + + // This here is why we *love* the C runtime + time_t current_time_seconds; + time(¤t_time_seconds); + struct tm* current_time = gmtime(¤t_time_seconds); + char output[256]; + strftime(output, arraysize(output), "%Y%m%dT%H:%M:%S", current_time); + result->AddAttr(kQnStamp, output, 1); + } + } + + return result; +} + + +} diff --git a/webrtc/libjingle/xmpp/presenceouttask.h b/webrtc/libjingle/xmpp/presenceouttask.h new file mode 100644 index 0000000000..88869df3cc --- /dev/null +++ b/webrtc/libjingle/xmpp/presenceouttask.h @@ -0,0 +1,37 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PRESENCEOUTTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_PRESENCEOUTTASK_H_ + +#include "webrtc/libjingle/xmpp/presencestatus.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +class PresenceOutTask : public XmppTask { +public: + explicit PresenceOutTask(XmppTaskParentInterface* parent) + : XmppTask(parent) {} + virtual ~PresenceOutTask() {} + + XmppReturnStatus Send(const PresenceStatus & s); + XmppReturnStatus SendDirected(const Jid & j, const PresenceStatus & s); + XmppReturnStatus SendProbe(const Jid& jid); + + virtual int ProcessStart(); +private: + XmlElement * TranslateStatus(const PresenceStatus & s); +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_PRESENCEOUTTASK_H_ diff --git a/webrtc/libjingle/xmpp/presencereceivetask.cc b/webrtc/libjingle/xmpp/presencereceivetask.cc new file mode 100644 index 0000000000..3ea7274c76 --- /dev/null +++ b/webrtc/libjingle/xmpp/presencereceivetask.cc @@ -0,0 +1,141 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/presencereceivetask.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/stringencode.h" + +namespace buzz { + +static bool IsUtf8FirstByte(int c) { + return (((c)&0x80)==0) || // is single byte + ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte +} + +PresenceReceiveTask::PresenceReceiveTask(XmppTaskParentInterface* parent) + : XmppTask(parent, XmppEngine::HL_TYPE) { +} + +PresenceReceiveTask::~PresenceReceiveTask() { + Stop(); +} + +int PresenceReceiveTask::ProcessStart() { + const XmlElement * stanza = NextStanza(); + if (stanza == NULL) { + return STATE_BLOCKED; + } + + Jid from(stanza->Attr(QN_FROM)); + HandlePresence(from, stanza); + + return STATE_START; +} + +bool PresenceReceiveTask::HandleStanza(const XmlElement * stanza) { + // Verify that this is a presence stanze + if (stanza->Name() != QN_PRESENCE) { + return false; // not sure if this ever happens. + } + + // Queue it up + QueueStanza(stanza); + + return true; +} + +void PresenceReceiveTask::HandlePresence(const Jid& from, + const XmlElement* stanza) { + if (stanza->Attr(QN_TYPE) == STR_ERROR) { + return; + } + + PresenceStatus status; + DecodeStatus(from, stanza, &status); + PresenceUpdate(status); +} + +void PresenceReceiveTask::DecodeStatus(const Jid& from, + const XmlElement* stanza, + PresenceStatus* presence_status) { + presence_status->set_jid(from); + if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) { + presence_status->set_available(false); + } else { + presence_status->set_available(true); + const XmlElement * status_elem = stanza->FirstNamed(QN_STATUS); + if (status_elem != NULL) { + presence_status->set_status(status_elem->BodyText()); + + // Truncate status messages longer than 300 bytes + if (presence_status->status().length() > 300) { + size_t len = 300; + + // Be careful not to split legal utf-8 chars in half + while (!IsUtf8FirstByte(presence_status->status()[len]) && len > 0) { + len -= 1; + } + std::string truncated(presence_status->status(), 0, len); + presence_status->set_status(truncated); + } + } + + const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY); + if (priority != NULL) { + int pri; + if (rtc::FromString(priority->BodyText(), &pri)) { + presence_status->set_priority(pri); + } + } + + const XmlElement * show = stanza->FirstNamed(QN_SHOW); + if (show == NULL || show->FirstChild() == NULL) { + presence_status->set_show(PresenceStatus::SHOW_ONLINE); + } else if (show->BodyText() == "away") { + presence_status->set_show(PresenceStatus::SHOW_AWAY); + } else if (show->BodyText() == "xa") { + presence_status->set_show(PresenceStatus::SHOW_XA); + } else if (show->BodyText() == "dnd") { + presence_status->set_show(PresenceStatus::SHOW_DND); + } else if (show->BodyText() == "chat") { + presence_status->set_show(PresenceStatus::SHOW_CHAT); + } else { + presence_status->set_show(PresenceStatus::SHOW_ONLINE); + } + + const XmlElement * caps = stanza->FirstNamed(QN_CAPS_C); + if (caps != NULL) { + std::string node = caps->Attr(QN_NODE); + std::string ver = caps->Attr(QN_VER); + std::string exts = caps->Attr(QN_EXT); + + presence_status->set_know_capabilities(true); + presence_status->set_caps_node(node); + presence_status->set_version(ver); + } + + const XmlElement* delay = stanza->FirstNamed(kQnDelayX); + if (delay != NULL) { + // Ideally we would parse this according to the Psuedo ISO-8601 rules + // that are laid out in JEP-0082: + // http://www.jabber.org/jeps/jep-0082.html + std::string stamp = delay->Attr(kQnStamp); + presence_status->set_sent_time(stamp); + } + + const XmlElement* nick = stanza->FirstNamed(QN_NICKNAME); + if (nick) { + presence_status->set_nick(nick->BodyText()); + } + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/presencereceivetask.h b/webrtc/libjingle/xmpp/presencereceivetask.h new file mode 100644 index 0000000000..20e6c79328 --- /dev/null +++ b/webrtc/libjingle/xmpp/presencereceivetask.h @@ -0,0 +1,56 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCERECEIVETASK_H_ +#define THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCERECEIVETASK_H_ + +#include "webrtc/base/sigslot.h" + +#include "webrtc/libjingle/xmpp/presencestatus.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +// A task to receive presence status callbacks from the XMPP server. +class PresenceReceiveTask : public XmppTask { + public: + // Arguments: + // parent a reference to task interface associated withe the XMPP client. + explicit PresenceReceiveTask(XmppTaskParentInterface* parent); + + // Shuts down the thread associated with this task. + virtual ~PresenceReceiveTask(); + + // Starts pulling queued status messages and dispatching them to the + // PresenceUpdate() callback. + virtual int ProcessStart(); + + // Slot for presence message callbacks + sigslot::signal1 PresenceUpdate; + + protected: + // Called by the XMPP engine when presence stanzas are received from the + // server. + virtual bool HandleStanza(const XmlElement * stanza); + + private: + // Handles presence stanzas by converting the data to PresenceStatus + // objects and passing those along to the SignalStatusUpadate() callback. + void HandlePresence(const Jid& from, const XmlElement * stanza); + + // Extracts presence information for the presence stanza sent form the + // server. + static void DecodeStatus(const Jid& from, const XmlElement * stanza, + PresenceStatus* status); +}; + +} // namespace buzz + +#endif // THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCERECEIVETASK_H_ diff --git a/webrtc/libjingle/xmpp/presencestatus.cc b/webrtc/libjingle/xmpp/presencestatus.cc new file mode 100644 index 0000000000..da6c64f1a2 --- /dev/null +++ b/webrtc/libjingle/xmpp/presencestatus.cc @@ -0,0 +1,45 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/presencestatus.h" + +namespace buzz { +PresenceStatus::PresenceStatus() + : pri_(0), + show_(SHOW_NONE), + available_(false), + e_code_(0), + feedback_probation_(false), + know_capabilities_(false), + voice_capability_(false), + pmuc_capability_(false), + video_capability_(false), + camera_capability_(false) { +} + +void PresenceStatus::UpdateWith(const PresenceStatus& new_value) { + if (!new_value.know_capabilities()) { + bool k = know_capabilities(); + bool p = voice_capability(); + std::string node = caps_node(); + std::string v = version(); + + *this = new_value; + + set_know_capabilities(k); + set_caps_node(node); + set_voice_capability(p); + set_version(v); + } else { + *this = new_value; + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/presencestatus.h b/webrtc/libjingle/xmpp/presencestatus.h new file mode 100644 index 0000000000..0261c72b06 --- /dev/null +++ b/webrtc/libjingle/xmpp/presencestatus.h @@ -0,0 +1,188 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCESTATUS_H_ +#define THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCESTATUS_H_ + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" + +namespace buzz { + +class PresenceStatus { +public: + PresenceStatus(); + ~PresenceStatus() {} + + // These are arranged in "priority order", i.e., if we see + // two statuses at the same priority but with different Shows, + // we will show the one with the highest show in the following + // order. + enum Show { + SHOW_NONE = 0, + SHOW_OFFLINE = 1, + SHOW_XA = 2, + SHOW_AWAY = 3, + SHOW_DND = 4, + SHOW_ONLINE = 5, + SHOW_CHAT = 6, + }; + + const Jid& jid() const { return jid_; } + int priority() const { return pri_; } + Show show() const { return show_; } + const std::string& status() const { return status_; } + const std::string& nick() const { return nick_; } + bool available() const { return available_ ; } + int error_code() const { return e_code_; } + const std::string& error_string() const { return e_str_; } + bool know_capabilities() const { return know_capabilities_; } + bool voice_capability() const { return voice_capability_; } + bool pmuc_capability() const { return pmuc_capability_; } + bool video_capability() const { return video_capability_; } + bool camera_capability() const { return camera_capability_; } + const std::string& caps_node() const { return caps_node_; } + const std::string& version() const { return version_; } + bool feedback_probation() const { return feedback_probation_; } + const std::string& sent_time() const { return sent_time_; } + + void set_jid(const Jid& jid) { jid_ = jid; } + void set_priority(int pri) { pri_ = pri; } + void set_show(Show show) { show_ = show; } + void set_status(const std::string& status) { status_ = status; } + void set_nick(const std::string& nick) { nick_ = nick; } + void set_available(bool a) { available_ = a; } + void set_error(int e_code, const std::string e_str) + { e_code_ = e_code; e_str_ = e_str; } + void set_know_capabilities(bool f) { know_capabilities_ = f; } + void set_voice_capability(bool f) { voice_capability_ = f; } + void set_pmuc_capability(bool f) { pmuc_capability_ = f; } + void set_video_capability(bool f) { video_capability_ = f; } + void set_camera_capability(bool f) { camera_capability_ = f; } + void set_caps_node(const std::string& f) { caps_node_ = f; } + void set_version(const std::string& v) { version_ = v; } + void set_feedback_probation(bool f) { feedback_probation_ = f; } + void set_sent_time(const std::string& time) { sent_time_ = time; } + + void UpdateWith(const PresenceStatus& new_value); + + bool HasQuietStatus() const { + if (status_.empty()) + return false; + return !(QuietStatus().empty()); + } + + // Knowledge of other clients' silly automatic status strings - + // Don't show these. + std::string QuietStatus() const { + if (jid_.resource().find("Psi") != std::string::npos) { + if (status_ == "Online" || + status_.find("Auto Status") != std::string::npos) + return STR_EMPTY; + } + if (jid_.resource().find("Gaim") != std::string::npos) { + if (status_ == "Sorry, I ran out for a bit!") + return STR_EMPTY; + } + return TrimStatus(status_); + } + + std::string ExplicitStatus() const { + std::string result = QuietStatus(); + if (result.empty()) { + result = ShowStatus(); + } + return result; + } + + std::string ShowStatus() const { + std::string result; + if (!available()) { + result = "Offline"; + } + else { + switch (show()) { + case SHOW_AWAY: + case SHOW_XA: + result = "Idle"; + break; + case SHOW_DND: + result = "Busy"; + break; + case SHOW_CHAT: + result = "Chatty"; + break; + default: + result = "Available"; + break; + } + } + return result; + } + + static std::string TrimStatus(const std::string& st) { + std::string s(st); + int j = 0; + bool collapsing = true; + for (unsigned int i = 0; i < s.length(); i+= 1) { + if (s[i] <= ' ' && s[i] >= 0) { + if (collapsing) { + continue; + } + else { + s[j] = ' '; + j += 1; + collapsing = true; + } + } + else { + s[j] = s[i]; + j += 1; + collapsing = false; + } + } + if (collapsing && j > 0) { + j -= 1; + } + s.erase(j, s.length()); + return s; + } + +private: + Jid jid_; + int pri_; + Show show_; + std::string status_; + std::string nick_; + bool available_; + int e_code_; + std::string e_str_; + bool feedback_probation_; + + // capabilities (valid only if know_capabilities_ + bool know_capabilities_; + bool voice_capability_; + bool pmuc_capability_; + bool video_capability_; + bool camera_capability_; + std::string caps_node_; + std::string version_; + + std::string sent_time_; // from the jabber:x:delay element +}; + +class MucPresenceStatus : public PresenceStatus { +}; + +} // namespace buzz + + +#endif // THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCESTATUS_H_ + diff --git a/webrtc/libjingle/xmpp/prexmppauth.h b/webrtc/libjingle/xmpp/prexmppauth.h new file mode 100644 index 0000000000..3a1e61064d --- /dev/null +++ b/webrtc/libjingle/xmpp/prexmppauth.h @@ -0,0 +1,71 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PREXMPPAUTH_H_ +#define WEBRTC_LIBJINGLE_XMPP_PREXMPPAUTH_H_ + +#include "webrtc/libjingle/xmpp/saslhandler.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { + class SocketAddress; +} + +namespace buzz { + +class Jid; +class SaslMechanism; + +class CaptchaChallenge { + public: + CaptchaChallenge() : captcha_needed_(false) {} + CaptchaChallenge(const std::string& token, const std::string& url) + : captcha_needed_(true), captcha_token_(token), captcha_image_url_(url) { + } + + bool captcha_needed() const { return captcha_needed_; } + const std::string& captcha_token() const { return captcha_token_; } + + // This url is relative to the gaia server. Once we have better tools + // for cracking URLs, we should probably make this a full URL + const std::string& captcha_image_url() const { return captcha_image_url_; } + + private: + bool captcha_needed_; + std::string captcha_token_; + std::string captcha_image_url_; +}; + +class PreXmppAuth : public SaslHandler { +public: + virtual ~PreXmppAuth() {} + + virtual void StartPreXmppAuth( + const Jid& jid, + const rtc::SocketAddress& server, + const rtc::CryptString& pass, + const std::string& auth_mechanism, + const std::string& auth_token) = 0; + + sigslot::signal0<> SignalAuthDone; + + virtual bool IsAuthDone() const = 0; + virtual bool IsAuthorized() const = 0; + virtual bool HadError() const = 0; + virtual int GetError() const = 0; + virtual CaptchaChallenge GetCaptchaChallenge() const = 0; + virtual std::string GetAuthMechanism() const = 0; + virtual std::string GetAuthToken() const = 0; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_PREXMPPAUTH_H_ diff --git a/webrtc/libjingle/xmpp/pubsub_task.cc b/webrtc/libjingle/xmpp/pubsub_task.cc new file mode 100644 index 0000000000..812cb95832 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsub_task.cc @@ -0,0 +1,201 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/pubsub_task.h" + +#include +#include +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/common.h" + +namespace buzz { + +PubsubTask::PubsubTask(XmppTaskParentInterface* parent, + const buzz::Jid& pubsub_node_jid) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_SENDER), + pubsub_node_jid_(pubsub_node_jid) { +} + +PubsubTask::~PubsubTask() { +} + +// Checks for pubsub publish events as well as responses to get IQs. +bool PubsubTask::HandleStanza(const buzz::XmlElement* stanza) { + const buzz::QName& stanza_name(stanza->Name()); + if (stanza_name == buzz::QN_MESSAGE) { + if (MatchStanzaFrom(stanza, pubsub_node_jid_)) { + const buzz::XmlElement* pubsub_event_item = + stanza->FirstNamed(QN_PUBSUB_EVENT); + if (pubsub_event_item != NULL) { + QueueStanza(pubsub_event_item); + return true; + } + } + } else if (stanza_name == buzz::QN_IQ) { + if (MatchResponseIq(stanza, pubsub_node_jid_, task_id())) { + const buzz::XmlElement* pubsub_item = stanza->FirstNamed(QN_PUBSUB); + if (pubsub_item != NULL) { + QueueStanza(pubsub_item); + return true; + } + } + } + return false; +} + +int PubsubTask::ProcessResponse() { + const buzz::XmlElement* stanza = NextStanza(); + if (stanza == NULL) { + return STATE_BLOCKED; + } + + if (stanza->Attr(buzz::QN_TYPE) == buzz::STR_ERROR) { + OnPubsubError(stanza->FirstNamed(buzz::QN_ERROR)); + return STATE_RESPONSE; + } + + const buzz::QName& stanza_name(stanza->Name()); + if (stanza_name == QN_PUBSUB_EVENT) { + HandlePubsubEventMessage(stanza); + } else if (stanza_name == QN_PUBSUB) { + HandlePubsubIqGetResponse(stanza); + } + + return STATE_RESPONSE; +} + +// Registers a function pointer to be called when the value of the pubsub +// node changes. +// Note that this does not actually change the XMPP pubsub +// subscription. All publish events are always received by everyone in the +// MUC. This function just controls whether the handle function will get +// called when the event is received. +bool PubsubTask::SubscribeToNode(const std::string& pubsub_node, + NodeHandler handler) { + subscribed_nodes_[pubsub_node] = handler; + std::unique_ptr get_iq_request( + MakeIq(buzz::STR_GET, pubsub_node_jid_, task_id())); + if (!get_iq_request) { + return false; + } + buzz::XmlElement* pubsub_element = new buzz::XmlElement(QN_PUBSUB, true); + buzz::XmlElement* items_element = new buzz::XmlElement(QN_PUBSUB_ITEMS, true); + + items_element->AddAttr(buzz::QN_NODE, pubsub_node); + pubsub_element->AddElement(items_element); + get_iq_request->AddElement(pubsub_element); + + if (SendStanza(get_iq_request.get()) != buzz::XMPP_RETURN_OK) { + return false; + } + + return true; +} + +void PubsubTask::UnsubscribeFromNode(const std::string& pubsub_node) { + subscribed_nodes_.erase(pubsub_node); +} + +void PubsubTask::OnPubsubError(const buzz::XmlElement* error_stanza) { +} + +// Checks for a pubsub event message like the following: +// +// +// +// +// +// +// +// +// +// +// +// It also checks for retraction event messages like the following: +// +// +// +// +// +// +// +// +void PubsubTask::HandlePubsubEventMessage( + const buzz::XmlElement* pubsub_event) { + ASSERT(pubsub_event->Name() == QN_PUBSUB_EVENT); + for (const buzz::XmlChild* child = pubsub_event->FirstChild(); + child != NULL; + child = child->NextChild()) { + const buzz::XmlElement* child_element = child->AsElement(); + const buzz::QName& child_name(child_element->Name()); + if (child_name == QN_PUBSUB_EVENT_ITEMS) { + HandlePubsubItems(child_element); + } + } +} + +// Checks for a response to an pubsub IQ get like the following: +// +// +// +// +// +// +// +// +// +// +void PubsubTask::HandlePubsubIqGetResponse( + const buzz::XmlElement* pubsub_iq_response) { + ASSERT(pubsub_iq_response->Name() == QN_PUBSUB); + for (const buzz::XmlChild* child = pubsub_iq_response->FirstChild(); + child != NULL; + child = child->NextChild()) { + const buzz::XmlElement* child_element = child->AsElement(); + const buzz::QName& child_name(child_element->Name()); + if (child_name == QN_PUBSUB_ITEMS) { + HandlePubsubItems(child_element); + } + } +} + +// Calls registered handlers in response to pubsub event or response to +// IQ pubsub get. +// 'items' is the child of a pubsub#event:event node or pubsub:pubsub node. +void PubsubTask::HandlePubsubItems(const buzz::XmlElement* items) { + ASSERT(items->HasAttr(QN_NODE)); + const std::string& node_name(items->Attr(QN_NODE)); + NodeSubscriptions::iterator iter = subscribed_nodes_.find(node_name); + if (iter != subscribed_nodes_.end()) { + NodeHandler handler = iter->second; + const buzz::XmlElement* item = items->FirstElement(); + while (item != NULL) { + const buzz::QName& item_name(item->Name()); + if (item_name != QN_PUBSUB_EVENT_ITEM && + item_name != QN_PUBSUB_EVENT_RETRACT && + item_name != QN_PUBSUB_ITEM) { + continue; + } + + (this->*handler)(item); + item = item->NextElement(); + } + return; + } +} + +} diff --git a/webrtc/libjingle/xmpp/pubsub_task.h b/webrtc/libjingle/xmpp/pubsub_task.h new file mode 100644 index 0000000000..b1923a0eb6 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsub_task.h @@ -0,0 +1,58 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUB_TASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_PUBSUB_TASK_H_ + +#include +#include +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +// Base class to help write pubsub tasks. +// In ProcessStart call SubscribeNode with namespaces of interest along with +// NodeHandlers. +// When pubsub notifications arrive and matches the namespace, the NodeHandlers +// will be called back. +class PubsubTask : public buzz::XmppTask { + public: + virtual ~PubsubTask(); + + protected: + typedef void (PubsubTask::*NodeHandler)(const buzz::XmlElement* node); + + PubsubTask(XmppTaskParentInterface* parent, const buzz::Jid& pubsub_node_jid); + + virtual bool HandleStanza(const buzz::XmlElement* stanza); + virtual int ProcessResponse(); + + bool SubscribeToNode(const std::string& pubsub_node, NodeHandler handler); + void UnsubscribeFromNode(const std::string& pubsub_node); + + // Called when there is an error. Derived class can do what it needs to. + virtual void OnPubsubError(const buzz::XmlElement* error_stanza); + + private: + typedef std::map NodeSubscriptions; + + void HandlePubsubIqGetResponse(const buzz::XmlElement* pubsub_iq_response); + void HandlePubsubEventMessage(const buzz::XmlElement* pubsub_event_message); + void HandlePubsubItems(const buzz::XmlElement* items); + + buzz::Jid pubsub_node_jid_; + NodeSubscriptions subscribed_nodes_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUB_TASK_H_ diff --git a/webrtc/libjingle/xmpp/pubsubclient.cc b/webrtc/libjingle/xmpp/pubsubclient.cc new file mode 100644 index 0000000000..41e4e983d3 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubclient.cc @@ -0,0 +1,129 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/pubsubclient.h" + +#include +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubtasks.h" + +namespace buzz { + +void PubSubClient::RequestItems() { + PubSubRequestTask* request_task = + new PubSubRequestTask(parent_, pubsubjid_, node_); + request_task->SignalResult.connect(this, &PubSubClient::OnRequestResult); + request_task->SignalError.connect(this, &PubSubClient::OnRequestError); + + PubSubReceiveTask* receive_task = + new PubSubReceiveTask(parent_, pubsubjid_, node_); + receive_task->SignalUpdate.connect(this, &PubSubClient::OnReceiveUpdate); + + receive_task->Start(); + request_task->Start(); +} + +void PubSubClient::PublishItem( + const std::string& itemid, XmlElement* payload, std::string* task_id_out) { + std::vector children; + children.push_back(payload); + PublishItem(itemid, children, task_id_out); +} + +void PubSubClient::PublishItem( + const std::string& itemid, const std::vector& children, + std::string* task_id_out) { + PubSubPublishTask* publish_task = + new PubSubPublishTask(parent_, pubsubjid_, node_, itemid, children); + publish_task->SignalError.connect(this, &PubSubClient::OnPublishError); + publish_task->SignalResult.connect(this, &PubSubClient::OnPublishResult); + publish_task->Start(); + if (task_id_out) { + *task_id_out = publish_task->task_id(); + } +} + +void PubSubClient::RetractItem( + const std::string& itemid, std::string* task_id_out) { + PubSubRetractTask* retract_task = + new PubSubRetractTask(parent_, pubsubjid_, node_, itemid); + retract_task->SignalError.connect(this, &PubSubClient::OnRetractError); + retract_task->SignalResult.connect(this, &PubSubClient::OnRetractResult); + retract_task->Start(); + if (task_id_out) { + *task_id_out = retract_task->task_id(); + } +} + +void PubSubClient::OnRequestResult(PubSubRequestTask* task, + const std::vector& items) { + SignalItems(this, items); +} + +void PubSubClient::OnRequestError(IqTask* task, + const XmlElement* stanza) { + SignalRequestError(this, stanza); +} + +void PubSubClient::OnReceiveUpdate(PubSubReceiveTask* task, + const std::vector& items) { + SignalItems(this, items); +} + +const XmlElement* GetItemFromStanza(const XmlElement* stanza) { + if (stanza != NULL) { + const XmlElement* pubsub = stanza->FirstNamed(QN_PUBSUB); + if (pubsub != NULL) { + const XmlElement* publish = pubsub->FirstNamed(QN_PUBSUB_PUBLISH); + if (publish != NULL) { + return publish->FirstNamed(QN_PUBSUB_ITEM); + } + } + } + return NULL; +} + +void PubSubClient::OnPublishResult(PubSubPublishTask* task) { + const XmlElement* item = GetItemFromStanza(task->stanza()); + SignalPublishResult(this, task->task_id(), item); +} + +void PubSubClient::OnPublishError(IqTask* task, + const XmlElement* error_stanza) { + PubSubPublishTask* publish_task = + static_cast(task); + const XmlElement* item = GetItemFromStanza(publish_task->stanza()); + SignalPublishError(this, publish_task->task_id(), item, error_stanza); +} + +void PubSubClient::OnRetractResult(PubSubRetractTask* task) { + SignalRetractResult(this, task->task_id()); +} + +void PubSubClient::OnRetractError(IqTask* task, + const XmlElement* stanza) { + PubSubRetractTask* retract_task = + static_cast(task); + SignalRetractError(this, retract_task->task_id(), stanza); +} + + +const std::string PubSubClient::GetPublisherNickFromPubSubItem( + const XmlElement* item_elem) { + if (item_elem == NULL) { + return ""; + } + + return Jid(item_elem->Attr(QN_ATTR_PUBLISHER)).resource(); +} +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/pubsubclient.h b/webrtc/libjingle/xmpp/pubsubclient.h new file mode 100644 index 0000000000..3044b9dc35 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubclient.h @@ -0,0 +1,111 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUBCLIENT_H_ +#define WEBRTC_LIBJINGLE_XMPP_PUBSUBCLIENT_H_ + +#include +#include + +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubtasks.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/sigslotrepeater.h" +#include "webrtc/base/task.h" + +// Easy to use clients built on top of the tasks for XEP-0060 +// (http://xmpp.org/extensions/xep-0060.html). + +namespace buzz { + +class Jid; +class XmlElement; +class XmppTaskParentInterface; + +// An easy-to-use pubsub client that handles the three tasks of +// getting, publishing, and listening for updates. Tied to a specific +// pubsub jid and node. All you have to do is RequestItems, listen +// for SignalItems and PublishItems. +class PubSubClient : public sigslot::has_slots<> { + public: + PubSubClient(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node) + : parent_(parent), + pubsubjid_(pubsubjid), + node_(node) {} + + const std::string& node() const { return node_; } + + // Requests the , which will be returned via + // SignalItems, or SignalRequestError if there is a failure. Should + // auto-subscribe. + void RequestItems(); + // Fired when either are returned or when + // are received. + sigslot::signal2&> SignalItems; + // Signal (this, error stanza) + sigslot::signal2 SignalRequestError; + // Signal (this, task_id, item, error stanza) + sigslot::signal4 SignalPublishError; + // Signal (this, task_id, item) + sigslot::signal3 SignalPublishResult; + // Signal (this, task_id, error stanza) + sigslot::signal3 SignalRetractError; + // Signal (this, task_id) + sigslot::signal2 SignalRetractResult; + + // Publish an item. Takes ownership of payload. + void PublishItem(const std::string& itemid, + XmlElement* payload, + std::string* task_id_out); + // Publish an item. Takes ownership of children. + void PublishItem(const std::string& itemid, + const std::vector& children, + std::string* task_id_out); + // Retract (delete) an item. + void RetractItem(const std::string& itemid, + std::string* task_id_out); + + // Get the publisher nick if it exists from the pubsub item. + const std::string GetPublisherNickFromPubSubItem(const XmlElement* item_elem); + + private: + void OnRequestError(IqTask* task, + const XmlElement* stanza); + void OnRequestResult(PubSubRequestTask* task, + const std::vector& items); + void OnReceiveUpdate(PubSubReceiveTask* task, + const std::vector& items); + void OnPublishResult(PubSubPublishTask* task); + void OnPublishError(IqTask* task, + const XmlElement* stanza); + void OnRetractResult(PubSubRetractTask* task); + void OnRetractError(IqTask* task, + const XmlElement* stanza); + + XmppTaskParentInterface* parent_; + Jid pubsubjid_; + std::string node_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUBCLIENT_H_ diff --git a/webrtc/libjingle/xmpp/pubsubclient_unittest.cc b/webrtc/libjingle/xmpp/pubsubclient_unittest.cc new file mode 100644 index 0000000000..ab4c853e56 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubclient_unittest.cc @@ -0,0 +1,279 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubclient.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +struct HandledPubSubItem { + std::string itemid; + std::string payload; +}; + +class TestPubSubItemsListener : public sigslot::has_slots<> { + public: + TestPubSubItemsListener() : error_count(0) {} + + void OnItems(buzz::PubSubClient*, + const std::vector& items) { + for (std::vector::const_iterator item = items.begin(); + item != items.end(); ++item) { + HandledPubSubItem handled_item; + handled_item.itemid = item->itemid; + if (item->elem->FirstElement() != NULL) { + handled_item.payload = item->elem->FirstElement()->Str(); + } + this->items.push_back(handled_item); + } + } + + void OnRequestError(buzz::PubSubClient* client, + const buzz::XmlElement* stanza) { + error_count++; + } + + void OnPublishResult(buzz::PubSubClient* client, + const std::string& task_id, + const buzz::XmlElement* item) { + result_task_id = task_id; + } + + void OnPublishError(buzz::PubSubClient* client, + const std::string& task_id, + const buzz::XmlElement* item, + const buzz::XmlElement* stanza) { + error_count++; + error_task_id = task_id; + } + + void OnRetractResult(buzz::PubSubClient* client, + const std::string& task_id) { + result_task_id = task_id; + } + + void OnRetractError(buzz::PubSubClient* client, + const std::string& task_id, + const buzz::XmlElement* stanza) { + error_count++; + error_task_id = task_id; + } + + std::vector items; + int error_count; + std::string error_task_id; + std::string result_task_id; +}; + +class PubSubClientTest : public testing::Test { + public: + PubSubClientTest() : + pubsubjid("room@domain.com"), + node("topic"), + itemid("key") { + runner.reset(new rtc::FakeTaskRunner()); + xmpp_client = new buzz::FakeXmppClient(runner.get()); + client.reset(new buzz::PubSubClient(xmpp_client, pubsubjid, node)); + listener.reset(new TestPubSubItemsListener()); + client->SignalItems.connect( + listener.get(), &TestPubSubItemsListener::OnItems); + client->SignalRequestError.connect( + listener.get(), &TestPubSubItemsListener::OnRequestError); + client->SignalPublishResult.connect( + listener.get(), &TestPubSubItemsListener::OnPublishResult); + client->SignalPublishError.connect( + listener.get(), &TestPubSubItemsListener::OnPublishError); + client->SignalRetractResult.connect( + listener.get(), &TestPubSubItemsListener::OnRetractResult); + client->SignalRetractError.connect( + listener.get(), &TestPubSubItemsListener::OnRetractError); + } + + std::unique_ptr runner; + // xmpp_client deleted by deleting runner. + buzz::FakeXmppClient* xmpp_client; + std::unique_ptr client; + std::unique_ptr listener; + buzz::Jid pubsubjid; + std::string node; + std::string itemid; +}; + +TEST_F(PubSubClientTest, TestRequest) { + client->RequestItems(); + + std::string expected_iq = + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + std::string result_iq = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + ASSERT_EQ(2U, listener->items.size()); + EXPECT_EQ("key0", listener->items[0].itemid); + EXPECT_EQ("", + listener->items[0].payload); + EXPECT_EQ("key1", listener->items[1].itemid); + EXPECT_EQ("", + listener->items[1].payload); + + std::string items_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(items_message)); + ASSERT_EQ(4U, listener->items.size()); + EXPECT_EQ("key0", listener->items[2].itemid); + EXPECT_EQ("", + listener->items[2].payload); + EXPECT_EQ("key1", listener->items[3].itemid); + EXPECT_EQ("", + listener->items[3].payload); +} + +TEST_F(PubSubClientTest, TestRequestError) { + std::string result_iq = + "" + " " + " " + " " + ""; + + client->RequestItems(); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->error_count); +} + +TEST_F(PubSubClientTest, TestPublish) { + buzz::XmlElement* payload = + new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value")); + + std::string task_id; + client->PublishItem(itemid, payload, &task_id); + + std::string expected_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + std::string result_iq = + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(task_id, listener->result_task_id); +} + +TEST_F(PubSubClientTest, TestPublishError) { + buzz::XmlElement* payload = + new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value")); + + std::string task_id; + client->PublishItem(itemid, payload, &task_id); + + std::string result_iq = + "" + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->error_count); + EXPECT_EQ(task_id, listener->error_task_id); +} + +TEST_F(PubSubClientTest, TestRetract) { + std::string task_id; + client->RetractItem(itemid, &task_id); + + std::string expected_iq = + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + std::string result_iq = + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(task_id, listener->result_task_id); +} + +TEST_F(PubSubClientTest, TestRetractError) { + std::string task_id; + client->RetractItem(itemid, &task_id); + + std::string result_iq = + "" + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->error_count); + EXPECT_EQ(task_id, listener->error_task_id); +} diff --git a/webrtc/libjingle/xmpp/pubsubstateclient.cc b/webrtc/libjingle/xmpp/pubsubstateclient.cc new file mode 100644 index 0000000000..1fae25dfb7 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubstateclient.cc @@ -0,0 +1,25 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/pubsubstateclient.h" + +namespace buzz { + +std::string PublishedNickKeySerializer::GetKey( + const std::string& publisher_nick, const std::string& published_nick) { + return published_nick; +} + +std::string PublisherAndPublishedNicksKeySerializer::GetKey( + const std::string& publisher_nick, const std::string& published_nick) { + return publisher_nick + ":" + published_nick; +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/pubsubstateclient.h b/webrtc/libjingle/xmpp/pubsubstateclient.h new file mode 100644 index 0000000000..07aa26dbad --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubstateclient.h @@ -0,0 +1,271 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_ +#define WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_ + +#include +#include +#include +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubclient.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/sigslotrepeater.h" + +namespace buzz { + +// To handle retracts correctly, we need to remember certain details +// about an item. We could just cache the entire XML element, but +// that would take more memory and require re-parsing. +struct StateItemInfo { + std::string published_nick; + std::string publisher_nick; +}; + +// Represents a PubSub state change. Usually, the key is the nick, +// but not always. It's a per-state-type thing. Look below on how keys are +// computed. +template +struct PubSubStateChange { + // The nick of the user changing the state. + std::string publisher_nick; + // The nick of the user whose state is changing. + std::string published_nick; + C old_state; + C new_state; +}; + +// Knows how to handle specific states and XML. +template +class PubSubStateSerializer { + public: + virtual ~PubSubStateSerializer() {} + virtual XmlElement* Write(const QName& state_name, const C& state) = 0; + virtual void Parse(const XmlElement* state_elem, C* state_out) = 0; +}; + +// Knows how to create "keys" for states, which determines their +// uniqueness. Most states are per-nick, but block is +// per-blocker-and-blockee. This is independent of itemid, especially +// in the case of presenter state. +class PubSubStateKeySerializer { + public: + virtual ~PubSubStateKeySerializer() {} + virtual std::string GetKey(const std::string& publisher_nick, + const std::string& published_nick) = 0; +}; + +class PublishedNickKeySerializer : public PubSubStateKeySerializer { + public: + virtual std::string GetKey(const std::string& publisher_nick, + const std::string& published_nick); +}; + +class PublisherAndPublishedNicksKeySerializer + : public PubSubStateKeySerializer { + public: + virtual std::string GetKey(const std::string& publisher_nick, + const std::string& published_nick); +}; + +// Adapts PubSubClient to be specifically suited for pub sub call +// states. Signals state changes and keeps track of keys, which are +// normally nicks. +template +class PubSubStateClient : public sigslot::has_slots<> { + public: + // Gets ownership of the serializers, but not the client. + PubSubStateClient(const std::string& publisher_nick, + PubSubClient* client, + const QName& state_name, + C default_state, + PubSubStateKeySerializer* key_serializer, + PubSubStateSerializer* state_serializer) + : publisher_nick_(publisher_nick), + client_(client), + state_name_(state_name), + default_state_(default_state) { + key_serializer_.reset(key_serializer); + state_serializer_.reset(state_serializer); + client_->SignalItems.connect( + this, &PubSubStateClient::OnItems); + client_->SignalPublishResult.connect( + this, &PubSubStateClient::OnPublishResult); + client_->SignalPublishError.connect( + this, &PubSubStateClient::OnPublishError); + client_->SignalRetractResult.connect( + this, &PubSubStateClient::OnRetractResult); + client_->SignalRetractError.connect( + this, &PubSubStateClient::OnRetractError); + } + + virtual ~PubSubStateClient() {} + + virtual void Publish(const std::string& published_nick, + const C& state, + std::string* task_id_out) { + std::string key = key_serializer_->GetKey(publisher_nick_, published_nick); + std::string itemid = state_name_.LocalPart() + ":" + key; + if (StatesEqual(state, default_state_)) { + client_->RetractItem(itemid, task_id_out); + } else { + XmlElement* state_elem = state_serializer_->Write(state_name_, state); + state_elem->AddAttr(QN_NICK, published_nick); + client_->PublishItem(itemid, state_elem, task_id_out); + } + } + + sigslot::signal1&> SignalStateChange; + // Signal (task_id, item). item is NULL for retract. + sigslot::signal2 SignalPublishResult; + // Signal (task_id, item, error stanza). item is NULL for retract. + sigslot::signal3 SignalPublishError; + + protected: + // return false if retracted item (no info or state given) + virtual bool ParseStateItem(const PubSubItem& item, + StateItemInfo* info_out, + C* state_out) { + const XmlElement* state_elem = item.elem->FirstNamed(state_name_); + if (state_elem == NULL) { + return false; + } + + info_out->publisher_nick = + client_->GetPublisherNickFromPubSubItem(item.elem); + info_out->published_nick = state_elem->Attr(QN_NICK); + state_serializer_->Parse(state_elem, state_out); + return true; + } + + virtual bool StatesEqual(const C& state1, const C& state2) { + return state1 == state2; + } + + PubSubClient* client() { return client_; } + const QName& state_name() { return state_name_; } + + private: + void OnItems(PubSubClient* pub_sub_client, + const std::vector& items) { + for (std::vector::const_iterator item = items.begin(); + item != items.end(); ++item) { + OnItem(*item); + } + } + + void OnItem(const PubSubItem& item) { + const std::string& itemid = item.itemid; + StateItemInfo info; + C new_state; + + bool retracted = !ParseStateItem(item, &info, &new_state); + if (retracted) { + bool known_itemid = + (info_by_itemid_.find(itemid) != info_by_itemid_.end()); + if (!known_itemid) { + // Nothing to retract, and nothing to publish. + // Probably a different state type. + return; + } else { + info = info_by_itemid_[itemid]; + info_by_itemid_.erase(itemid); + new_state = default_state_; + } + } else { + // TODO: Assert new key matches the known key. It + // shouldn't change! + info_by_itemid_[itemid] = info; + } + + std::string key = key_serializer_->GetKey( + info.publisher_nick, info.published_nick); + bool has_old_state = (state_by_key_.find(key) != state_by_key_.end()); + C old_state = has_old_state ? state_by_key_[key] : default_state_; + if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) { + // Nothing change, so don't bother signalling. + return; + } + + if (retracted || StatesEqual(new_state, default_state_)) { + // We treat a default state similar to a retract. + state_by_key_.erase(key); + } else { + state_by_key_[key] = new_state; + } + + PubSubStateChange change; + if (!retracted) { + // Retracts do not have publisher information. + change.publisher_nick = info.publisher_nick; + } + change.published_nick = info.published_nick; + change.old_state = old_state; + change.new_state = new_state; + SignalStateChange(change); + } + + void OnPublishResult(PubSubClient* pub_sub_client, + const std::string& task_id, + const XmlElement* item) { + SignalPublishResult(task_id, item); + } + + void OnPublishError(PubSubClient* pub_sub_client, + const std::string& task_id, + const buzz::XmlElement* item, + const buzz::XmlElement* stanza) { + SignalPublishError(task_id, item, stanza); + } + + void OnRetractResult(PubSubClient* pub_sub_client, + const std::string& task_id) { + // There's no point in differentiating between publish and retract + // errors, so we simplify by making them both signal a publish + // result. + const XmlElement* item = NULL; + SignalPublishResult(task_id, item); + } + + void OnRetractError(PubSubClient* pub_sub_client, + const std::string& task_id, + const buzz::XmlElement* stanza) { + // There's no point in differentiating between publish and retract + // errors, so we simplify by making them both signal a publish + // error. + const XmlElement* item = NULL; + SignalPublishError(task_id, item, stanza); + } + + std::string publisher_nick_; + PubSubClient* client_; + const QName state_name_; + C default_state_; + std::unique_ptr key_serializer_; + std::unique_ptr > state_serializer_; + // key => state + std::map state_by_key_; + // itemid => StateItemInfo + std::map info_by_itemid_; + + RTC_DISALLOW_COPY_AND_ASSIGN(PubSubStateClient); +}; +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_ diff --git a/webrtc/libjingle/xmpp/pubsubtasks.cc b/webrtc/libjingle/xmpp/pubsubtasks.cc new file mode 100644 index 0000000000..d6532598b5 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubtasks.cc @@ -0,0 +1,204 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/pubsubtasks.h" + +#include +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/receivetask.h" + +// An implementation of the tasks for XEP-0060 +// (http://xmpp.org/extensions/xep-0060.html). + +namespace buzz { + +namespace { + +bool IsPubSubEventItemsElem(const XmlElement* stanza, + const std::string& expected_node) { + if (stanza->Name() != QN_MESSAGE) { + return false; + } + + const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT); + if (event_elem == NULL) { + return false; + } + + const XmlElement* items_elem = event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS); + if (items_elem == NULL) { + return false; + } + + const std::string& actual_node = items_elem->Attr(QN_NODE); + return (actual_node == expected_node); +} + + +// Creates +XmlElement* CreatePubSubItemsElem(const std::string& node) { + XmlElement* items_elem = new XmlElement(QN_PUBSUB_ITEMS, false); + items_elem->AddAttr(QN_NODE, node); + XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, false); + pubsub_elem->AddElement(items_elem); + return pubsub_elem; +} + +// Creates payload... +// Takes ownership of payload. +XmlElement* CreatePubSubPublishItemElem( + const std::string& node, + const std::string& itemid, + const std::vector& children) { + XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true); + XmlElement* publish_elem = new XmlElement(QN_PUBSUB_PUBLISH, false); + publish_elem->AddAttr(QN_NODE, node); + XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false); + item_elem->AddAttr(QN_ID, itemid); + for (std::vector::const_iterator child = children.begin(); + child != children.end(); ++child) { + item_elem->AddElement(*child); + } + publish_elem->AddElement(item_elem); + pubsub_elem->AddElement(publish_elem); + return pubsub_elem; +} + +// Creates payload... +// Takes ownership of payload. +XmlElement* CreatePubSubRetractItemElem(const std::string& node, + const std::string& itemid) { + XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true); + XmlElement* retract_elem = new XmlElement(QN_PUBSUB_RETRACT, false); + retract_elem->AddAttr(QN_NODE, node); + retract_elem->AddAttr(QN_NOTIFY, "true"); + XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false); + item_elem->AddAttr(QN_ID, itemid); + retract_elem->AddElement(item_elem); + pubsub_elem->AddElement(retract_elem); + return pubsub_elem; +} + +void ParseItem(const XmlElement* item_elem, + std::vector* items) { + PubSubItem item; + item.itemid = item_elem->Attr(QN_ID); + item.elem = item_elem; + items->push_back(item); +} + +// Right now, s are treated the same as items with empty +// payloads. We may want to change it in the future, but right now +// it's sufficient for our needs. +void ParseRetract(const XmlElement* retract_elem, + std::vector* items) { + ParseItem(retract_elem, items); +} + +void ParseEventItemsElem(const XmlElement* stanza, + std::vector* items) { + const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT); + if (event_elem != NULL) { + const XmlElement* items_elem = + event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS); + if (items_elem != NULL) { + for (const XmlElement* item_elem = + items_elem->FirstNamed(QN_PUBSUB_EVENT_ITEM); + item_elem != NULL; + item_elem = item_elem->NextNamed(QN_PUBSUB_EVENT_ITEM)) { + ParseItem(item_elem, items); + } + for (const XmlElement* retract_elem = + items_elem->FirstNamed(QN_PUBSUB_EVENT_RETRACT); + retract_elem != NULL; + retract_elem = retract_elem->NextNamed(QN_PUBSUB_EVENT_RETRACT)) { + ParseRetract(retract_elem, items); + } + } + } +} + +void ParsePubSubItemsElem(const XmlElement* stanza, + std::vector* items) { + const XmlElement* pubsub_elem = stanza->FirstNamed(QN_PUBSUB); + if (pubsub_elem != NULL) { + const XmlElement* items_elem = pubsub_elem->FirstNamed(QN_PUBSUB_ITEMS); + if (items_elem != NULL) { + for (const XmlElement* item_elem = items_elem->FirstNamed(QN_PUBSUB_ITEM); + item_elem != NULL; + item_elem = item_elem->NextNamed(QN_PUBSUB_ITEM)) { + ParseItem(item_elem, items); + } + } + } +} + +} // namespace + +PubSubRequestTask::PubSubRequestTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node) + : IqTask(parent, STR_GET, pubsubjid, CreatePubSubItemsElem(node)) { +} + +void PubSubRequestTask::HandleResult(const XmlElement* stanza) { + std::vector items; + ParsePubSubItemsElem(stanza, &items); + SignalResult(this, items); +} + +int PubSubReceiveTask::ProcessStart() { + if (SignalUpdate.is_empty()) { + return STATE_DONE; + } + return ReceiveTask::ProcessStart(); +} + +bool PubSubReceiveTask::WantsStanza(const XmlElement* stanza) { + return MatchStanzaFrom(stanza, pubsubjid_) && + IsPubSubEventItemsElem(stanza, node_) && !SignalUpdate.is_empty(); +} + +void PubSubReceiveTask::ReceiveStanza(const XmlElement* stanza) { + std::vector items; + ParseEventItemsElem(stanza, &items); + SignalUpdate(this, items); +} + +PubSubPublishTask::PubSubPublishTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node, + const std::string& itemid, + const std::vector& children) + : IqTask(parent, STR_SET, pubsubjid, + CreatePubSubPublishItemElem(node, itemid, children)), + itemid_(itemid) { +} + +void PubSubPublishTask::HandleResult(const XmlElement* stanza) { + SignalResult(this); +} + +PubSubRetractTask::PubSubRetractTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node, + const std::string& itemid) + : IqTask(parent, STR_SET, pubsubjid, + CreatePubSubRetractItemElem(node, itemid)), + itemid_(itemid) { +} + +void PubSubRetractTask::HandleResult(const XmlElement* stanza) { + SignalResult(this); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/pubsubtasks.h b/webrtc/libjingle/xmpp/pubsubtasks.h new file mode 100644 index 0000000000..2f56fa87cc --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubtasks.h @@ -0,0 +1,114 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUBTASKS_H_ +#define WEBRTC_LIBJINGLE_XMPP_PUBSUBTASKS_H_ + +#include + +#include "webrtc/libjingle/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/receivetask.h" +#include "webrtc/base/sigslot.h" + +namespace buzz { + +// A PubSub itemid + payload. Useful for signaling items. +struct PubSubItem { + std::string itemid; + // The entire , owned by the stanza handler. To keep a + // reference after handling, make a copy. + const XmlElement* elem; +}; + +// An IqTask which gets a for a particular jid and +// node, parses the items in the response and signals the items. +class PubSubRequestTask : public IqTask { + public: + PubSubRequestTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node); + + sigslot::signal2&> SignalResult; + // SignalError inherited by IqTask. + private: + virtual void HandleResult(const XmlElement* stanza); +}; + +// A ReceiveTask which listens for of a particular +// pubsub JID and node and then signals them items. +class PubSubReceiveTask : public ReceiveTask { + public: + PubSubReceiveTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node) + : ReceiveTask(parent), + pubsubjid_(pubsubjid), + node_(node) { + } + + virtual int ProcessStart(); + sigslot::signal2&> SignalUpdate; + + protected: + virtual bool WantsStanza(const XmlElement* stanza); + virtual void ReceiveStanza(const XmlElement* stanza); + + private: + Jid pubsubjid_; + std::string node_; +}; + +// An IqTask which publishes a to a particular +// pubsub jid and node. +class PubSubPublishTask : public IqTask { + public: + // Takes ownership of children + PubSubPublishTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node, + const std::string& itemid, + const std::vector& children); + + const std::string& itemid() const { return itemid_; } + + sigslot::signal1 SignalResult; + + private: + // SignalError inherited by IqTask. + virtual void HandleResult(const XmlElement* stanza); + + std::string itemid_; +}; + +// An IqTask which publishes a to a particular +// pubsub jid and node. +class PubSubRetractTask : public IqTask { + public: + PubSubRetractTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node, + const std::string& itemid); + + const std::string& itemid() const { return itemid_; } + + sigslot::signal1 SignalResult; + + private: + // SignalError inherited by IqTask. + virtual void HandleResult(const XmlElement* stanza); + + std::string itemid_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUBTASKS_H_ diff --git a/webrtc/libjingle/xmpp/pubsubtasks_unittest.cc b/webrtc/libjingle/xmpp/pubsubtasks_unittest.cc new file mode 100644 index 0000000000..79e656bb0b --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubtasks_unittest.cc @@ -0,0 +1,281 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubtasks.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +struct HandledPubSubItem { + std::string itemid; + std::string payload; +}; + +class TestPubSubTasksListener : public sigslot::has_slots<> { + public: + TestPubSubTasksListener() : result_count(0), error_count(0) {} + + void OnReceiveUpdate(buzz::PubSubReceiveTask* task, + const std::vector& items) { + OnItems(items); + } + + void OnRequestResult(buzz::PubSubRequestTask* task, + const std::vector& items) { + OnItems(items); + } + + void OnItems(const std::vector& items) { + for (std::vector::const_iterator item = items.begin(); + item != items.end(); ++item) { + HandledPubSubItem handled_item; + handled_item.itemid = item->itemid; + if (item->elem->FirstElement() != NULL) { + handled_item.payload = item->elem->FirstElement()->Str(); + } + this->items.push_back(handled_item); + } + } + + void OnPublishResult(buzz::PubSubPublishTask* task) { + ++result_count; + } + + void OnRetractResult(buzz::PubSubRetractTask* task) { + ++result_count; + } + + void OnError(buzz::IqTask* task, const buzz::XmlElement* stanza) { + ++error_count; + } + + std::vector items; + int result_count; + int error_count; +}; + +class PubSubTasksTest : public testing::Test { + public: + PubSubTasksTest() : + pubsubjid("room@domain.com"), + node("topic"), + itemid("key") { + runner.reset(new rtc::FakeTaskRunner()); + client = new buzz::FakeXmppClient(runner.get()); + listener.reset(new TestPubSubTasksListener()); + } + + std::unique_ptr runner; + // Client deleted by deleting runner. + buzz::FakeXmppClient* client; + std::unique_ptr listener; + buzz::Jid pubsubjid; + std::string node; + std::string itemid; +}; + +TEST_F(PubSubTasksTest, TestRequest) { + buzz::PubSubRequestTask* task = + new buzz::PubSubRequestTask(client, pubsubjid, node); + task->SignalResult.connect( + listener.get(), &TestPubSubTasksListener::OnRequestResult); + task->Start(); + + std::string expected_iq = + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str()); + + std::string result_iq = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + + ASSERT_EQ(2U, listener->items.size()); + EXPECT_EQ("key0", listener->items[0].itemid); + EXPECT_EQ("", + listener->items[0].payload); + EXPECT_EQ("key1", listener->items[1].itemid); + EXPECT_EQ("", + listener->items[1].payload); +} + +TEST_F(PubSubTasksTest, TestRequestError) { + std::string result_iq = + "" + " " + " " + " " + ""; + + buzz::PubSubRequestTask* task = + new buzz::PubSubRequestTask(client, pubsubjid, node); + task->SignalResult.connect( + listener.get(), &TestPubSubTasksListener::OnRequestResult); + task->SignalError.connect( + listener.get(), &TestPubSubTasksListener::OnError); + task->Start(); + client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + + EXPECT_EQ(0, listener->result_count); + EXPECT_EQ(1, listener->error_count); +} + +TEST_F(PubSubTasksTest, TestReceive) { + std::string items_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + buzz::PubSubReceiveTask* task = + new buzz::PubSubReceiveTask(client, pubsubjid, node); + task->SignalUpdate.connect( + listener.get(), &TestPubSubTasksListener::OnReceiveUpdate); + task->Start(); + client->HandleStanza(buzz::XmlElement::ForStr(items_message)); + + ASSERT_EQ(2U, listener->items.size()); + EXPECT_EQ("key0", listener->items[0].itemid); + EXPECT_EQ( + "", + listener->items[0].payload); + EXPECT_EQ("key1", listener->items[1].itemid); + EXPECT_EQ( + "", + listener->items[1].payload); +} + +TEST_F(PubSubTasksTest, TestPublish) { + buzz::XmlElement* payload = + new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value")); + std::string expected_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + std::vector children; + children.push_back(payload); + buzz::PubSubPublishTask* task = + new buzz::PubSubPublishTask(client, pubsubjid, node, itemid, children); + task->SignalResult.connect( + listener.get(), &TestPubSubTasksListener::OnPublishResult); + task->Start(); + + ASSERT_EQ(1U, client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str()); + + std::string result_iq = + ""; + + client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + + EXPECT_EQ(1, listener->result_count); + EXPECT_EQ(0, listener->error_count); +} + +TEST_F(PubSubTasksTest, TestPublishError) { + buzz::XmlElement* payload = + new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value")); + + std::vector children; + children.push_back(payload); + buzz::PubSubPublishTask* task = + new buzz::PubSubPublishTask(client, pubsubjid, node, itemid, children); + task->SignalResult.connect( + listener.get(), &TestPubSubTasksListener::OnPublishResult); + task->SignalError.connect( + listener.get(), &TestPubSubTasksListener::OnError); + task->Start(); + + std::string result_iq = + "" + " " + " " + " " + ""; + + client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + + EXPECT_EQ(0, listener->result_count); + EXPECT_EQ(1, listener->error_count); +} + +TEST_F(PubSubTasksTest, TestRetract) { + buzz::PubSubRetractTask* task = + new buzz::PubSubRetractTask(client, pubsubjid, node, itemid); + task->SignalResult.connect( + listener.get(), &TestPubSubTasksListener::OnRetractResult); + task->SignalError.connect( + listener.get(), &TestPubSubTasksListener::OnError); + task->Start(); + + std::string expected_iq = + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str()); + + std::string result_iq = + ""; + + client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + + EXPECT_EQ(1, listener->result_count); + EXPECT_EQ(0, listener->error_count); +} diff --git a/webrtc/libjingle/xmpp/receivetask.cc b/webrtc/libjingle/xmpp/receivetask.cc new file mode 100644 index 0000000000..9d4687cf54 --- /dev/null +++ b/webrtc/libjingle/xmpp/receivetask.cc @@ -0,0 +1,34 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/receivetask.h" + +namespace buzz { + +bool ReceiveTask::HandleStanza(const XmlElement* stanza) { + if (WantsStanza(stanza)) { + QueueStanza(stanza); + return true; + } + + return false; +} + +int ReceiveTask::ProcessStart() { + const XmlElement* stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + + ReceiveStanza(stanza); + return STATE_START; +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/receivetask.h b/webrtc/libjingle/xmpp/receivetask.h new file mode 100644 index 0000000000..b776746be9 --- /dev/null +++ b/webrtc/libjingle/xmpp/receivetask.h @@ -0,0 +1,41 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_RECEIVETASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_RECEIVETASK_H_ + +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +// A base class for receiving stanzas. Override WantsStanza to +// indicate that a stanza should be received and ReceiveStanza to +// process it. Once started, ReceiveStanza will be called for all +// stanzas that return true when passed to WantsStanza. This saves +// you from having to remember how to setup the queueing and the task +// states, etc. +class ReceiveTask : public XmppTask { + public: + explicit ReceiveTask(XmppTaskParentInterface* parent) : + XmppTask(parent, XmppEngine::HL_TYPE) {} + virtual int ProcessStart(); + + protected: + virtual bool HandleStanza(const XmlElement* stanza); + + // Return true if the stanza should be received. + virtual bool WantsStanza(const XmlElement* stanza) = 0; + // Process the received stanza. + virtual void ReceiveStanza(const XmlElement* stanza) = 0; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_RECEIVETASK_H_ diff --git a/webrtc/libjingle/xmpp/rostermodule.h b/webrtc/libjingle/xmpp/rostermodule.h new file mode 100644 index 0000000000..85d5d34407 --- /dev/null +++ b/webrtc/libjingle/xmpp/rostermodule.h @@ -0,0 +1,331 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_ROSTERMODULE_H_ +#define WEBRTC_LIBJINGLE_XMPP_ROSTERMODULE_H_ + +#include "webrtc/libjingle/xmpp/module.h" + +namespace buzz { + +class XmppRosterModule; + +// The main way you initialize and use the module would be like this: +// XmppRosterModule *roster_module = XmppRosterModule::Create(); +// roster_module->RegisterEngine(engine); +// roster_module->BroadcastPresence(); +// roster_module->RequestRosterUpdate(); + +//! This enum captures the valid values for the show attribute in a presence +//! stanza +enum XmppPresenceShow +{ + XMPP_PRESENCE_CHAT = 0, + XMPP_PRESENCE_DEFAULT = 1, + XMPP_PRESENCE_AWAY = 2, + XMPP_PRESENCE_XA = 3, + XMPP_PRESENCE_DND = 4, +}; + +//! These are the valid subscription states in a roster contact. This +//! represents the combination of the subscription and ask attributes +enum XmppSubscriptionState +{ + XMPP_SUBSCRIPTION_NONE = 0, + XMPP_SUBSCRIPTION_NONE_ASKED = 1, + XMPP_SUBSCRIPTION_TO = 2, + XMPP_SUBSCRIPTION_FROM = 3, + XMPP_SUBSCRIPTION_FROM_ASKED = 4, + XMPP_SUBSCRIPTION_BOTH = 5, +}; + +//! These represent the valid types of presence stanzas for managing +//! subscriptions +enum XmppSubscriptionRequestType +{ + XMPP_REQUEST_SUBSCRIBE = 0, + XMPP_REQUEST_UNSUBSCRIBE = 1, + XMPP_REQUEST_SUBSCRIBED = 2, + XMPP_REQUEST_UNSUBSCRIBED = 3, +}; + +enum XmppPresenceAvailable { + XMPP_PRESENCE_UNAVAILABLE = 0, + XMPP_PRESENCE_AVAILABLE = 1, + XMPP_PRESENCE_ERROR = 2, +}; + +enum XmppPresenceConnectionStatus { + XMPP_CONNECTION_STATUS_UNKNOWN = 0, + // Status set by the server while the user is being rung. + XMPP_CONNECTION_STATUS_CONNECTING = 1, + // Status set by the client when the user has accepted the ring but before + // the client has joined the call. + XMPP_CONNECTION_STATUS_JOINING = 2, + // Status set by the client as part of joining the call. + XMPP_CONNECTION_STATUS_CONNECTED = 3, + XMPP_CONNECTION_STATUS_HANGUP = 4, +}; + +//! Presence Information +//! This class stores both presence information for outgoing presence and is +//! returned by methods in XmppRosterModule to represent recieved incoming +//! presence information. When this class is writeable (non-const) then each +//! update to any property will set the inner xml. Setting the raw_xml will +//! rederive all of the other properties. +class XmppPresence { +public: + virtual ~XmppPresence() {} + + //! Create a new Presence + //! This is typically only used when sending a directed presence + static XmppPresence* Create(); + + //! The Jid of for the presence information. + //! Typically this will be a full Jid with resource specified. + virtual const Jid jid() const = 0; + + //! Is the contact available? + virtual XmppPresenceAvailable available() const = 0; + + //! Sets if the user is available or not + virtual XmppReturnStatus set_available(XmppPresenceAvailable available) = 0; + + //! The show value of the presence info + virtual XmppPresenceShow presence_show() const = 0; + + //! Set the presence show value + virtual XmppReturnStatus set_presence_show(XmppPresenceShow show) = 0; + + //! The Priority of the presence info + virtual int priority() const = 0; + + //! Set the priority of the presence + virtual XmppReturnStatus set_priority(int priority) = 0; + + //! The plain text status of the presence info. + //! If there are multiple status because of language, this will either be a + //! status that is not tagged for language or the first available + virtual const std::string status() const = 0; + + //! Sets the status for the presence info. + //! If there is more than one status present already then this will remove + //! them all and replace it with one status element we no specified language + virtual XmppReturnStatus set_status(const std::string& status) = 0; + + //! The connection status + virtual XmppPresenceConnectionStatus connection_status() const = 0; + + //! The focus obfuscated GAIA id + virtual const std::string google_user_id() const = 0; + + //! The nickname in the presence + virtual const std::string nickname() const = 0; + + //! The raw xml of the presence update + virtual const XmlElement* raw_xml() const = 0; + + //! Sets the raw presence stanza for the presence update + //! This will cause all other data items in this structure to be rederived + virtual XmppReturnStatus set_raw_xml(const XmlElement * xml) = 0; +}; + +//! A contact as given by the server +class XmppRosterContact { +public: + virtual ~XmppRosterContact() {} + + //! Create a new roster contact + //! This is typically only used when doing a roster update/add + static XmppRosterContact* Create(); + + //! The jid for the contact. + //! Typically this will be a bare Jid. + virtual const Jid jid() const = 0; + + //! Sets the jid for the roster contact update + virtual XmppReturnStatus set_jid(const Jid& jid) = 0; + + //! The name (nickname) stored for this contact + virtual const std::string name() const = 0; + + //! Sets the name + virtual XmppReturnStatus set_name(const std::string& name) = 0; + + //! The Presence subscription state stored on the server for this contact + //! This is never settable and will be ignored when generating a roster + //! add/update request + virtual XmppSubscriptionState subscription_state() const = 0; + + //! The number of Groups applied to this contact + virtual size_t GetGroupCount() const = 0; + + //! Gets a Group applied to the contact based on index. + //! range + virtual const std::string GetGroup(size_t index) const = 0; + + //! Adds a group to this contact. + //! This will return a bad argument error if the group is already there. + virtual XmppReturnStatus AddGroup(const std::string& group) = 0; + + //! Removes a group from the contact. + //! This will return an error if the group cannot be found in the group list. + virtual XmppReturnStatus RemoveGroup(const std::string& group) = 0; + + //! The raw xml for this roster contact + virtual const XmlElement* raw_xml() const = 0; + + //! Sets the raw presence stanza for the contact update/add + //! This will cause all other data items in this structure to be rederived + virtual XmppReturnStatus set_raw_xml(const XmlElement * xml) = 0; +}; + +//! The XmppRosterHandler is an interface for callbacks from the module +class XmppRosterHandler { +public: + virtual ~XmppRosterHandler() {} + + //! A request for a subscription has come in. + //! Typically, the UI will ask the user if it is okay to let the requester + //! get presence notifications for the user. The response is send back + //! by calling ApproveSubscriber or CancelSubscriber. + virtual void SubscriptionRequest(XmppRosterModule* roster, + const Jid& requesting_jid, + XmppSubscriptionRequestType type, + const XmlElement* raw_xml) = 0; + + //! Some type of presence error has occured + virtual void SubscriptionError(XmppRosterModule* roster, + const Jid& from, + const XmlElement* raw_xml) = 0; + + virtual void RosterError(XmppRosterModule* roster, + const XmlElement* raw_xml) = 0; + + //! New presence information has come in + //! The user is notified with the presence object directly. This info is also + //! added to the store accessable from the engine. + virtual void IncomingPresenceChanged(XmppRosterModule* roster, + const XmppPresence* presence) = 0; + + //! A contact has changed + //! This indicates that the data for a contact may have changed. No + //! contacts have been added or removed. + virtual void ContactChanged(XmppRosterModule* roster, + const XmppRosterContact* old_contact, + size_t index) = 0; + + //! A set of contacts have been added + //! These contacts may have been added in response to the original roster + //! request or due to a "roster push" from the server. + virtual void ContactsAdded(XmppRosterModule* roster, + size_t index, size_t number) = 0; + + //! A contact has been removed + //! This contact has been removed form the list. + virtual void ContactRemoved(XmppRosterModule* roster, + const XmppRosterContact* removed_contact, + size_t index) = 0; + +}; + +//! An XmppModule for handle roster and presence functionality +class XmppRosterModule : public XmppModule { +public: + //! Creates a new XmppRosterModule + static XmppRosterModule * Create(); + virtual ~XmppRosterModule() {} + + //! Sets the roster handler (callbacks) for the module + virtual XmppReturnStatus set_roster_handler(XmppRosterHandler * handler) = 0; + + //! Gets the roster handler for the module + virtual XmppRosterHandler* roster_handler() = 0; + + // USER PRESENCE STATE ------------------------------------------------------- + + //! Gets the aggregate outgoing presence + //! This object is non-const and be edited directly. No update is sent + //! to the server until a Broadcast is sent + virtual XmppPresence* outgoing_presence() = 0; + + //! Broadcasts that the user is available. + //! Nothing with respect to presence is sent until this is called. + virtual XmppReturnStatus BroadcastPresence() = 0; + + //! Sends a directed presence to a Jid + //! Note that the client doesn't store where directed presence notifications + //! have been sent. The server can keep the appropriate state + virtual XmppReturnStatus SendDirectedPresence(const XmppPresence* presence, + const Jid& to_jid) = 0; + + // INCOMING PRESENCE STATUS -------------------------------------------------- + + //! Returns the number of incoming presence data recorded + virtual size_t GetIncomingPresenceCount() = 0; + + //! Returns an incoming presence datum based on index + virtual const XmppPresence* GetIncomingPresence(size_t index) = 0; + + //! Gets the number of presence data for a bare Jid + //! There may be a datum per resource + virtual size_t GetIncomingPresenceForJidCount(const Jid& jid) = 0; + + //! Returns a single presence data for a Jid based on index + virtual const XmppPresence* GetIncomingPresenceForJid(const Jid& jid, + size_t index) = 0; + + // ROSTER MANAGEMENT --------------------------------------------------------- + + //! Requests an update of the roster from the server + //! This must be called to initialize the client side cache of the roster + //! After this is sent the server should keep this module apprised of any + //! changes. + virtual XmppReturnStatus RequestRosterUpdate() = 0; + + //! Returns the number of contacts in the roster + virtual size_t GetRosterContactCount() = 0; + + //! Returns a contact by index + virtual const XmppRosterContact* GetRosterContact(size_t index) = 0; + + //! Finds a contact by Jid + virtual const XmppRosterContact* FindRosterContact(const Jid& jid) = 0; + + //! Send a request to the server to add a contact + //! Note that the contact won't show up in the roster until the server can + //! respond. This happens async when the socket is being serviced + virtual XmppReturnStatus RequestRosterChange( + const XmppRosterContact* contact) = 0; + + //! Request that the server remove a contact + //! The jabber protocol specifies that the server should also cancel any + //! subscriptions when this is done. Like adding, this contact won't be + //! removed until the server responds. + virtual XmppReturnStatus RequestRosterRemove(const Jid& jid) = 0; + + // SUBSCRIPTION MANAGEMENT --------------------------------------------------- + + //! Request a subscription to presence notifications form a Jid + virtual XmppReturnStatus RequestSubscription(const Jid& jid) = 0; + + //! Cancel a subscription to presence notifications from a Jid + virtual XmppReturnStatus CancelSubscription(const Jid& jid) = 0; + + //! Approve a request to deliver presence notifications to a jid + virtual XmppReturnStatus ApproveSubscriber(const Jid& jid) = 0; + + //! Deny or cancel presence notification deliver to a jid + virtual XmppReturnStatus CancelSubscriber(const Jid& jid) = 0; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_ROSTERMODULE_H_ diff --git a/webrtc/libjingle/xmpp/rostermodule_unittest.cc b/webrtc/libjingle/xmpp/rostermodule_unittest.cc new file mode 100644 index 0000000000..18f2025357 --- /dev/null +++ b/webrtc/libjingle/xmpp/rostermodule_unittest.cc @@ -0,0 +1,832 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/rostermodule.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/gunit.h" + +#define TEST_OK(x) EXPECT_EQ((x),XMPP_RETURN_OK) +#define TEST_BADARGUMENT(x) EXPECT_EQ((x),XMPP_RETURN_BADARGUMENT) + + +namespace buzz { + +class RosterModuleTest; + +static void +WriteString(std::ostream& os, const std::string& str) { + os<jid().Str()); + os<<" available:"<available(); + os<<" presence_show:"; + WritePresenceShow(os, presence->presence_show()); + os<<" priority:"<priority(); + os<<" status:"; + WriteString(os, presence->status()); + os<<"]"<raw_xml()->Str(); +} + +static void +WriteContact(std::ostream& os, const XmppRosterContact* contact) { + if (contact == NULL) { + os<<"NULL"; + return; + } + + os<<"[Contact jid:"; + WriteString(os, contact->jid().Str()); + os<<" name:"; + WriteString(os, contact->name()); + os<<" subscription_state:"; + WriteSubscriptionState(os, contact->subscription_state()); + os<<" groups:["; + for(size_t i=0; i < contact->GetGroupCount(); ++i) { + os<<(i==0?"":", "); + WriteString(os, contact->GetGroup(i)); + } + os<<"]]"<raw_xml()->Str(); +} + +//! This session handler saves all calls to a string. These are events and +//! data delivered form the engine to application code. +class XmppTestRosterHandler : public XmppRosterHandler { +public: + XmppTestRosterHandler() {} + virtual ~XmppTestRosterHandler() {} + + virtual void SubscriptionRequest(XmppRosterModule*, + const Jid& requesting_jid, + XmppSubscriptionRequestType type, + const XmlElement* raw_xml) { + ss_<<"[SubscriptionRequest Jid:" << requesting_jid.Str()<<" type:"; + WriteSubscriptionRequestType(ss_, type); + ss_<<"]"<Str(); + } + + //! Some type of presence error has occured + virtual void SubscriptionError(XmppRosterModule*, + const Jid& from, + const XmlElement* raw_xml) { + ss_<<"[SubscriptionError from:"<Str(); + } + + virtual void RosterError(XmppRosterModule*, + const XmlElement* raw_xml) { + ss_<<"[RosterError]"<Str(); + } + + //! New presence information has come in + //! The user is notified with the presence object directly. This info is also + //! added to the store accessable from the engine. + virtual void IncomingPresenceChanged(XmppRosterModule*, + const XmppPresence* presence) { + ss_<<"[IncomingPresenceChanged presence:"; + WritePresence(ss_, presence); + ss_<<"]"; + } + + //! A contact has changed + //! This indicates that the data for a contact may have changed. No + //! contacts have been added or removed. + virtual void ContactChanged(XmppRosterModule* roster, + const XmppRosterContact* old_contact, + size_t index) { + ss_<<"[ContactChanged old_contact:"; + WriteContact(ss_, old_contact); + ss_<<" index:"<GetRosterContact(index)); + ss_<<"]"; + } + + //! A set of contacts have been added + //! These contacts may have been added in response to the original roster + //! request or due to a "roster push" from the server. + virtual void ContactsAdded(XmppRosterModule* roster, + size_t index, size_t number) { + ss_<<"[ContactsAdded index:"<GetRosterContact(index+i)); + } + ss_<<"]"; + } + + //! A contact has been removed + //! This contact has been removed form the list. + virtual void ContactRemoved(XmppRosterModule*, + const XmppRosterContact* removed_contact, + size_t index) { + ss_<<"[ContactRemoved old_contact:"; + WriteContact(ss_, removed_contact); + ss_<<" index:"<AddAttr(QN_STATUS, STR_PSTN_CONFERENCE_STATUS_CONNECTING); + XmlElement presence_xml(QN_PRESENCE); + presence_xml.AddElement(status); + std::unique_ptr presence(XmppPresence::Create()); + presence->set_raw_xml(&presence_xml); + EXPECT_EQ(presence->connection_status(), XMPP_CONNECTION_STATUS_CONNECTING); +} + +TEST_F(RosterModuleTest, TestOutgoingPresence) { + std::stringstream dump; + + std::unique_ptr engine(XmppEngine::Create()); + XmppTestHandler handler(engine.get()); + XmppTestRosterHandler roster_handler; + + std::unique_ptr roster(XmppRosterModule::Create()); + roster->set_roster_handler(&roster_handler); + + // Configure the roster module + roster->RegisterEngine(engine.get()); + + // Set up callbacks + engine->SetOutputHandler(&handler); + engine->AddStanzaHandler(&handler); + engine->SetSessionHandler(&handler); + + // Set up minimal login info + engine->SetUser(Jid("david@my-server")); + // engine->SetPassword("david"); + + // Do the whole login handshake + RunLogin(this, engine.get(), &handler); + EXPECT_EQ("", handler.OutputActivity()); + + // Set some presence and broadcast it + TEST_OK(roster->outgoing_presence()-> + set_available(XMPP_PRESENCE_AVAILABLE)); + TEST_OK(roster->outgoing_presence()->set_priority(-37)); + TEST_OK(roster->outgoing_presence()->set_presence_show(XMPP_PRESENCE_DND)); + TEST_OK(roster->outgoing_presence()-> + set_status("I'm off to the races!<>&")); + TEST_OK(roster->BroadcastPresence()); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "-37" + "dnd" + "I'm off to the races!<>&" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Try some more + TEST_OK(roster->outgoing_presence()-> + set_available(XMPP_PRESENCE_UNAVAILABLE)); + TEST_OK(roster->outgoing_presence()->set_priority(0)); + TEST_OK(roster->outgoing_presence()->set_presence_show(XMPP_PRESENCE_XA)); + TEST_OK(roster->outgoing_presence()->set_status("Gone fishin'")); + TEST_OK(roster->BroadcastPresence()); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "xa" + "Gone fishin'" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Okay -- we are back on + TEST_OK(roster->outgoing_presence()-> + set_available(XMPP_PRESENCE_AVAILABLE)); + TEST_BADARGUMENT(roster->outgoing_presence()->set_priority(128)); + TEST_OK(roster->outgoing_presence()-> + set_presence_show(XMPP_PRESENCE_DEFAULT)); + TEST_OK(roster->outgoing_presence()->set_status("Cookin' wit gas")); + TEST_OK(roster->BroadcastPresence()); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "Cookin' wit gas" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Set it via XML + XmlElement presence_input(QN_PRESENCE); + presence_input.AddAttr(QN_TYPE, "unavailable"); + presence_input.AddElement(new XmlElement(QN_PRIORITY)); + presence_input.AddText("42", 1); + presence_input.AddElement(new XmlElement(QN_STATUS)); + presence_input.AddAttr(QN_XML_LANG, "es", 1); + presence_input.AddText("Hola Amigos!", 1); + presence_input.AddElement(new XmlElement(QN_STATUS)); + presence_input.AddText("Hey there, friend!", 1); + TEST_OK(roster->outgoing_presence()->set_raw_xml(&presence_input)); + TEST_OK(roster->BroadcastPresence()); + + WritePresence(dump, roster->outgoing_presence()); + EXPECT_EQ(dump.str(), + "[Presence jid: available:0 presence_show:[default] " + "priority:42 status:Hey there, friend!]" + "" + "42" + "Hola Amigos!" + "Hey there, friend!" + ""); + dump.str(""); + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "42" + "Hola Amigos!" + "Hey there, friend!" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Construct a directed presence + std::unique_ptr directed_presence(XmppPresence::Create()); + TEST_OK(directed_presence->set_available(XMPP_PRESENCE_AVAILABLE)); + TEST_OK(directed_presence->set_priority(120)); + TEST_OK(directed_presence->set_status("*very* available")); + TEST_OK(roster->SendDirectedPresence(directed_presence.get(), + Jid("myhoney@honey.net"))); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "120" + "*very* available" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); +} + +TEST_F(RosterModuleTest, TestIncomingPresence) { + std::unique_ptr engine(XmppEngine::Create()); + XmppTestHandler handler(engine.get()); + XmppTestRosterHandler roster_handler; + + std::unique_ptr roster(XmppRosterModule::Create()); + roster->set_roster_handler(&roster_handler); + + // Configure the roster module + roster->RegisterEngine(engine.get()); + + // Set up callbacks + engine->SetOutputHandler(&handler); + engine->AddStanzaHandler(&handler); + engine->SetSessionHandler(&handler); + + // Set up minimal login info + engine->SetUser(Jid("david@my-server")); + // engine->SetPassword("david"); + + // Do the whole login handshake + RunLogin(this, engine.get(), &handler); + EXPECT_EQ("", handler.OutputActivity()); + + // Load up with a bunch of data + std::string input; + input = "" + "" + "-10" + "xa" + "Off bowling" + "" + "" + "20" + "Looking for toes..." + "" + "" + "10" + "Throwing rocks" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[IncomingPresenceChanged " + "presence:[Presence jid:maude@example.net/studio available:1 " + "presence_show:[default] priority:0 status:]" + "]" + "[IncomingPresenceChanged " + "presence:[Presence jid:walter@example.net/home available:1 " + "presence_show:xa priority:-10 status:Off bowling]" + "" + "-10" + "xa" + "Off bowling" + "]" + "[IncomingPresenceChanged " + "presence:[Presence jid:walter@example.net/alley available:1 " + "presence_show:[default] " + "priority:20 status:Looking for toes...]" + "" + "20" + "Looking for toes..." + "]" + "[IncomingPresenceChanged " + "presence:[Presence jid:donny@example.net/alley available:1 " + "presence_show:[default] priority:10 status:Throwing rocks]" + "" + "10" + "Throwing rocks" + "]"); + EXPECT_EQ(handler.OutputActivity(), ""); + handler.SessionActivity(); // Ignore the session output + + // Now look at the data structure we've built + EXPECT_EQ(roster->GetIncomingPresenceCount(), static_cast(4)); + EXPECT_EQ(roster->GetIncomingPresenceForJidCount(Jid("maude@example.net")), + static_cast(1)); + EXPECT_EQ(roster->GetIncomingPresenceForJidCount(Jid("walter@example.net")), + static_cast(2)); + + const XmppPresence * presence; + presence = roster->GetIncomingPresenceForJid(Jid("walter@example.net"), 1); + + std::stringstream dump; + WritePresence(dump, presence); + EXPECT_EQ(dump.str(), + "[Presence jid:walter@example.net/alley available:1 " + "presence_show:[default] priority:20 status:Looking for toes...]" + "" + "20" + "Looking for toes..." + ""); + dump.str(""); + + // Maude took off... + input = "" + "Stealing my rug back" + "-10" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[IncomingPresenceChanged " + "presence:[Presence jid:maude@example.net/studio available:0 " + "presence_show:[default] priority:-10 " + "status:Stealing my rug back]" + "" + "Stealing my rug back" + "-10" + "]"); + EXPECT_EQ(handler.OutputActivity(), ""); + handler.SessionActivity(); // Ignore the session output +} + +TEST_F(RosterModuleTest, TestPresenceSubscription) { + std::unique_ptr engine(XmppEngine::Create()); + XmppTestHandler handler(engine.get()); + XmppTestRosterHandler roster_handler; + + std::unique_ptr roster(XmppRosterModule::Create()); + roster->set_roster_handler(&roster_handler); + + // Configure the roster module + roster->RegisterEngine(engine.get()); + + // Set up callbacks + engine->SetOutputHandler(&handler); + engine->AddStanzaHandler(&handler); + engine->SetSessionHandler(&handler); + + // Set up minimal login info + engine->SetUser(Jid("david@my-server")); + // engine->SetPassword("david"); + + // Do the whole login handshake + RunLogin(this, engine.get(), &handler); + EXPECT_EQ("", handler.OutputActivity()); + + // Test incoming requests + std::string input; + input = + "" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[SubscriptionRequest Jid:maude@example.net type:subscribe]" + "" + "[SubscriptionRequest Jid:maude@example.net type:unsubscribe]" + "" + "[SubscriptionRequest Jid:maude@example.net type:subscribed]" + "" + "[SubscriptionRequest Jid:maude@example.net type:unsubscribe]" + ""); + EXPECT_EQ(handler.OutputActivity(), ""); + handler.SessionActivity(); // Ignore the session output + + TEST_OK(roster->RequestSubscription(Jid("maude@example.net"))); + TEST_OK(roster->CancelSubscription(Jid("maude@example.net"))); + TEST_OK(roster->ApproveSubscriber(Jid("maude@example.net"))); + TEST_OK(roster->CancelSubscriber(Jid("maude@example.net"))); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "" + "" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); +} + +TEST_F(RosterModuleTest, TestRosterReceive) { + std::unique_ptr engine(XmppEngine::Create()); + XmppTestHandler handler(engine.get()); + XmppTestRosterHandler roster_handler; + + std::unique_ptr roster(XmppRosterModule::Create()); + roster->set_roster_handler(&roster_handler); + + // Configure the roster module + roster->RegisterEngine(engine.get()); + + // Set up callbacks + engine->SetOutputHandler(&handler); + engine->AddStanzaHandler(&handler); + engine->SetSessionHandler(&handler); + + // Set up minimal login info + engine->SetUser(Jid("david@my-server")); + // engine->SetPassword("david"); + + // Do the whole login handshake + RunLogin(this, engine.get(), &handler); + EXPECT_EQ("", handler.OutputActivity()); + + // Request a roster update + TEST_OK(roster->RequestRosterUpdate()); + + EXPECT_EQ(roster_handler.StrClear(),""); + EXPECT_EQ(handler.OutputActivity(), + "" + "" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Prime the roster with a starting set + std::string input = + "" + "" + "" + "Business Partners" + "" + "" + "Friends" + "Bowling Team" + "Bowling League" + "" + "" + "Friends" + "Bowling Team" + "Bowling League" + "" + "" + "Business Partners" + "" + "" + "Bowling League" + "" + "" + ""; + + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[ContactsAdded index:0 number:5 " + "0:[Contact jid:maude@example.net name:Maude Lebowski " + "subscription_state:none_asked " + "groups:[Business Partners]]" + "" + "Business Partners" + " " + "1:[Contact jid:walter@example.net name:Walter Sobchak " + "subscription_state:both " + "groups:[Friends, Bowling Team, Bowling League]]" + "" + "Friends" + "Bowling Team" + "Bowling League" + " " + "2:[Contact jid:donny@example.net name:Donny " + "subscription_state:both " + "groups:[Friends, Bowling Team, Bowling League]]" + "" + "Friends" + "Bowling Team" + "Bowling League" + " " + "3:[Contact jid:jeffrey@example.net name:The Big Lebowski " + "subscription_state:to " + "groups:[Business Partners]]" + "" + "Business Partners" + " " + "4:[Contact jid:jesus@example.net name:Jesus Quintana " + "subscription_state:from groups:[Bowling League]]" + "" + "Bowling League" + "]"); + EXPECT_EQ(handler.OutputActivity(), ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Request that someone be added + std::unique_ptr contact(XmppRosterContact::Create()); + TEST_OK(contact->set_jid(Jid("brandt@example.net"))); + TEST_OK(contact->set_name("Brandt")); + TEST_OK(contact->AddGroup("Business Partners")); + TEST_OK(contact->AddGroup("Watchers")); + TEST_OK(contact->AddGroup("Friends")); + TEST_OK(contact->RemoveGroup("Friends")); // Maybe not... + TEST_OK(roster->RequestRosterChange(contact.get())); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "" + "" + "Business Partners" + "Watchers" + "" + "" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Get the push from the server + input = + "" + "" + "" + "" + "Business Partners" + "Watchers" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[ContactsAdded index:5 number:1 " + "5:[Contact jid:brandt@example.net name:Brandt " + "subscription_state:none " + "groups:[Business Partners, Watchers]]" + "" + "Business Partners" + "Watchers" + "]"); + EXPECT_EQ(handler.OutputActivity(), + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Get a contact update + input = + "" + "" + "" + "Friends" + "Bowling Team" + "Bowling League" + "Not wrong, just an..." + "" + "" + ""; + + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[ContactChanged " + "old_contact:[Contact jid:walter@example.net name:Walter Sobchak " + "subscription_state:both " + "groups:[Friends, Bowling Team, Bowling League]]" + "" + "Friends" + "Bowling Team" + "Bowling League" + " " + "index:1 " + "new_contact:[Contact jid:walter@example.net name:Walter Sobchak " + "subscription_state:both " + "groups:[Friends, Bowling Team, Bowling League, " + "Not wrong, just an...]]" + "" + "Friends" + "Bowling Team" + "Bowling League" + "Not wrong, just an..." + "]"); + EXPECT_EQ(handler.OutputActivity(), + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Remove a contact + TEST_OK(roster->RequestRosterRemove(Jid("jesus@example.net"))); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Response from the server + input = + "" + "" + "" + "" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[ContactRemoved " + "old_contact:[Contact jid:jesus@example.net name:Jesus Quintana " + "subscription_state:from groups:[Bowling League]]" + "" + "Bowling League" + " index:4]"); + EXPECT_EQ(handler.OutputActivity(), + ""); + EXPECT_EQ(handler.SessionActivity(), ""); +} + +} diff --git a/webrtc/libjingle/xmpp/rostermoduleimpl.cc b/webrtc/libjingle/xmpp/rostermoduleimpl.cc new file mode 100644 index 0000000000..b9752896ef --- /dev/null +++ b/webrtc/libjingle/xmpp/rostermoduleimpl.cc @@ -0,0 +1,1064 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include +#include +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/rostermoduleimpl.h" +#include "webrtc/base/common.h" +#include "webrtc/base/stringencode.h" + +namespace buzz { + +// enum prase and persist helpers ---------------------------------------------- +static bool +StringToPresenceShow(const std::string& input, XmppPresenceShow* show) { + // If this becomes a perf issue we can use a hash or a map here + if (STR_SHOW_AWAY == input) + *show = XMPP_PRESENCE_AWAY; + else if (STR_SHOW_DND == input) + *show = XMPP_PRESENCE_DND; + else if (STR_SHOW_XA == input) + *show = XMPP_PRESENCE_XA; + else if (STR_SHOW_CHAT == input) + *show = XMPP_PRESENCE_CHAT; + else if (STR_EMPTY == input) + *show = XMPP_PRESENCE_DEFAULT; + else + return false; + + return true; +} + +static bool +PresenceShowToString(XmppPresenceShow show, const char** output) { + switch(show) { + case XMPP_PRESENCE_AWAY: + *output = STR_SHOW_AWAY; + return true; + case XMPP_PRESENCE_CHAT: + *output = STR_SHOW_CHAT; + return true; + case XMPP_PRESENCE_XA: + *output = STR_SHOW_XA; + return true; + case XMPP_PRESENCE_DND: + *output = STR_SHOW_DND; + return true; + case XMPP_PRESENCE_DEFAULT: + *output = STR_EMPTY; + return true; + } + + *output = STR_EMPTY; + return false; +} + +static bool +StringToSubscriptionState(const std::string& subscription, + const std::string& ask, + XmppSubscriptionState* state) +{ + if (ask == "subscribe") + { + if (subscription == "none") { + *state = XMPP_SUBSCRIPTION_NONE_ASKED; + return true; + } + if (subscription == "from") { + *state = XMPP_SUBSCRIPTION_FROM_ASKED; + return true; + } + } else if (ask == STR_EMPTY) + { + if (subscription == "none") { + *state = XMPP_SUBSCRIPTION_NONE; + return true; + } + if (subscription == "from") { + *state = XMPP_SUBSCRIPTION_FROM; + return true; + } + if (subscription == "to") { + *state = XMPP_SUBSCRIPTION_TO; + return true; + } + if (subscription == "both") { + *state = XMPP_SUBSCRIPTION_BOTH; + return true; + } + } + + return false; +} + +static bool +StringToSubscriptionRequestType(const std::string& string, + XmppSubscriptionRequestType* type) +{ + if (string == "subscribe") + *type = XMPP_REQUEST_SUBSCRIBE; + else if (string == "unsubscribe") + *type = XMPP_REQUEST_UNSUBSCRIBE; + else if (string == "subscribed") + *type = XMPP_REQUEST_SUBSCRIBED; + else if (string == "unsubscribed") + *type = XMPP_REQUEST_UNSUBSCRIBED; + else + return false; + return true; +} + +// XmppPresenceImpl class ------------------------------------------------------ +XmppPresence* +XmppPresence::Create() { + return new XmppPresenceImpl(); +} + +XmppPresenceImpl::XmppPresenceImpl() { +} + +const Jid +XmppPresenceImpl::jid() const { + if (!raw_xml_) + return Jid(); + + return Jid(raw_xml_->Attr(QN_FROM)); +} + +XmppPresenceAvailable +XmppPresenceImpl::available() const { + if (!raw_xml_) + return XMPP_PRESENCE_UNAVAILABLE; + + if (raw_xml_->Attr(QN_TYPE) == "unavailable") + return XMPP_PRESENCE_UNAVAILABLE; + else if (raw_xml_->Attr(QN_TYPE) == "error") + return XMPP_PRESENCE_ERROR; + else + return XMPP_PRESENCE_AVAILABLE; +} + +XmppReturnStatus +XmppPresenceImpl::set_available(XmppPresenceAvailable available) { + if (!raw_xml_) + CreateRawXmlSkeleton(); + + if (available == XMPP_PRESENCE_AVAILABLE) + raw_xml_->ClearAttr(QN_TYPE); + else if (available == XMPP_PRESENCE_UNAVAILABLE) + raw_xml_->SetAttr(QN_TYPE, "unavailable"); + else if (available == XMPP_PRESENCE_ERROR) + raw_xml_->SetAttr(QN_TYPE, "error"); + return XMPP_RETURN_OK; +} + +XmppPresenceShow +XmppPresenceImpl::presence_show() const { + if (!raw_xml_) + return XMPP_PRESENCE_DEFAULT; + + XmppPresenceShow show = XMPP_PRESENCE_DEFAULT; + StringToPresenceShow(raw_xml_->TextNamed(QN_SHOW), &show); + return show; +} + +XmppReturnStatus +XmppPresenceImpl::set_presence_show(XmppPresenceShow show) { + if (!raw_xml_) + CreateRawXmlSkeleton(); + + const char* show_string; + + if(!PresenceShowToString(show, &show_string)) + return XMPP_RETURN_BADARGUMENT; + + raw_xml_->ClearNamedChildren(QN_SHOW); + + if (show!=XMPP_PRESENCE_DEFAULT) { + raw_xml_->AddElement(new XmlElement(QN_SHOW)); + raw_xml_->AddText(show_string, 1); + } + + return XMPP_RETURN_OK; +} + +int +XmppPresenceImpl::priority() const { + if (!raw_xml_) + return 0; + + int raw_priority = 0; + if (!rtc::FromString(raw_xml_->TextNamed(QN_PRIORITY), &raw_priority)) + raw_priority = 0; + if (raw_priority < -128) + raw_priority = -128; + if (raw_priority > 127) + raw_priority = 127; + + return raw_priority; +} + +XmppReturnStatus +XmppPresenceImpl::set_priority(int priority) { + if (!raw_xml_) + CreateRawXmlSkeleton(); + + if (priority < -128 || priority > 127) + return XMPP_RETURN_BADARGUMENT; + + raw_xml_->ClearNamedChildren(QN_PRIORITY); + if (0 != priority) { + std::string priority_string; + if (rtc::ToString(priority, &priority_string)) { + raw_xml_->AddElement(new XmlElement(QN_PRIORITY)); + raw_xml_->AddText(priority_string, 1); + } + } + + return XMPP_RETURN_OK; +} + +const std::string +XmppPresenceImpl::status() const { + if (!raw_xml_) + return STR_EMPTY; + + XmlElement* status_element; + XmlElement* element; + + // Search for a status element with no xml:lang attribute on it. if we can't + // find that then just return the first status element in the stanza. + for (status_element = element = raw_xml_->FirstNamed(QN_STATUS); + element; + element = element->NextNamed(QN_STATUS)) { + if (!element->HasAttr(QN_XML_LANG)) { + status_element = element; + break; + } + } + + if (status_element) { + return status_element->BodyText(); + } + + return STR_EMPTY; +} + +XmppReturnStatus +XmppPresenceImpl::set_status(const std::string& status) { + if (!raw_xml_) + CreateRawXmlSkeleton(); + + raw_xml_->ClearNamedChildren(QN_STATUS); + + if (status != STR_EMPTY) { + raw_xml_->AddElement(new XmlElement(QN_STATUS)); + raw_xml_->AddText(status, 1); + } + + return XMPP_RETURN_OK; +} + +XmppPresenceConnectionStatus +XmppPresenceImpl::connection_status() const { + if (!raw_xml_) + return XMPP_CONNECTION_STATUS_UNKNOWN; + + XmlElement* con = raw_xml_->FirstNamed(QN_GOOGLE_PSTN_CONFERENCE_STATUS); + if (con) { + std::string status = con->Attr(QN_ATTR_STATUS); + if (status == STR_PSTN_CONFERENCE_STATUS_CONNECTING) + return XMPP_CONNECTION_STATUS_CONNECTING; + else if (status == STR_PSTN_CONFERENCE_STATUS_CONNECTED) + return XMPP_CONNECTION_STATUS_CONNECTED; + else if (status == STR_PSTN_CONFERENCE_STATUS_JOINING) + return XMPP_CONNECTION_STATUS_JOINING; + else if (status == STR_PSTN_CONFERENCE_STATUS_HANGUP) + return XMPP_CONNECTION_STATUS_HANGUP; + } + + return XMPP_CONNECTION_STATUS_CONNECTED; +} + +const std::string +XmppPresenceImpl::google_user_id() const { + if (!raw_xml_) + return std::string(); + + XmlElement* muc_user_x = raw_xml_->FirstNamed(QN_MUC_USER_X); + if (muc_user_x) { + XmlElement* muc_user_item = muc_user_x->FirstNamed(QN_MUC_USER_ITEM); + if (muc_user_item) { + return muc_user_item->Attr(QN_GOOGLE_USER_ID); + } + } + + return std::string(); +} + +const std::string +XmppPresenceImpl::nickname() const { + if (!raw_xml_) + return std::string(); + + XmlElement* nickname = raw_xml_->FirstNamed(QN_NICKNAME); + if (nickname) { + return nickname->BodyText(); + } + + return std::string(); +} + +const XmlElement* +XmppPresenceImpl::raw_xml() const { + if (!raw_xml_) + const_cast(this)->CreateRawXmlSkeleton(); + return raw_xml_.get(); +} + +XmppReturnStatus +XmppPresenceImpl::set_raw_xml(const XmlElement * xml) { + if (!xml || + xml->Name() != QN_PRESENCE) + return XMPP_RETURN_BADARGUMENT; + + raw_xml_.reset(new XmlElement(*xml)); + return XMPP_RETURN_OK; +} + +void +XmppPresenceImpl::CreateRawXmlSkeleton() { + raw_xml_.reset(new XmlElement(QN_PRESENCE)); +} + +// XmppRosterContactImpl ------------------------------------------------------- +XmppRosterContact* +XmppRosterContact::Create() { + return new XmppRosterContactImpl(); +} + +XmppRosterContactImpl::XmppRosterContactImpl() { + ResetGroupCache(); +} + +void +XmppRosterContactImpl::SetXmlFromWire(const XmlElement* xml) { + ResetGroupCache(); + if (xml) + raw_xml_.reset(new XmlElement(*xml)); + else + raw_xml_.reset(NULL); +} + +void +XmppRosterContactImpl::ResetGroupCache() { + group_count_ = -1; + group_index_returned_ = -1; + group_returned_ = NULL; +} + +const Jid +XmppRosterContactImpl::jid() const { + return Jid(raw_xml_->Attr(QN_JID)); +} + +XmppReturnStatus +XmppRosterContactImpl::set_jid(const Jid& jid) +{ + if (!raw_xml_) + CreateRawXmlSkeleton(); + + if (!jid.IsValid()) + return XMPP_RETURN_BADARGUMENT; + + raw_xml_->SetAttr(QN_JID, jid.Str()); + + return XMPP_RETURN_OK; +} + +const std::string +XmppRosterContactImpl::name() const { + return raw_xml_->Attr(QN_NAME); +} + +XmppReturnStatus +XmppRosterContactImpl::set_name(const std::string& name) { + if (!raw_xml_) + CreateRawXmlSkeleton(); + + if (name == STR_EMPTY) + raw_xml_->ClearAttr(QN_NAME); + else + raw_xml_->SetAttr(QN_NAME, name); + + return XMPP_RETURN_OK; +} + +XmppSubscriptionState +XmppRosterContactImpl::subscription_state() const { + if (!raw_xml_) + return XMPP_SUBSCRIPTION_NONE; + + XmppSubscriptionState state = XMPP_SUBSCRIPTION_NONE; + + if (StringToSubscriptionState(raw_xml_->Attr(QN_SUBSCRIPTION), + raw_xml_->Attr(QN_ASK), + &state)) + return state; + + return XMPP_SUBSCRIPTION_NONE; +} + +size_t +XmppRosterContactImpl::GetGroupCount() const { + if (!raw_xml_) + return 0; + + if (-1 == group_count_) { + XmlElement *group_element = raw_xml_->FirstNamed(QN_ROSTER_GROUP); + int group_count = 0; + while(group_element) { + group_count++; + group_element = group_element->NextNamed(QN_ROSTER_GROUP); + } + + ASSERT(group_count > 0); // protect the cast + XmppRosterContactImpl * me = const_cast(this); + me->group_count_ = group_count; + } + + return group_count_; +} + +const std::string +XmppRosterContactImpl::GetGroup(size_t index) const { + if (index >= GetGroupCount()) + return STR_EMPTY; + + // We cache the last group index and element that we returned. This way + // going through the groups in order is order n and not n^2. This could be + // enhanced if necessary by starting at the cached value if the index asked + // is after the cached one. + if (group_index_returned_ >= 0 && + index == static_cast(group_index_returned_) + 1) + { + XmppRosterContactImpl * me = const_cast(this); + me->group_returned_ = group_returned_->NextNamed(QN_ROSTER_GROUP); + ASSERT(group_returned_ != NULL); + me->group_index_returned_++; + } else if (group_index_returned_ < 0 || + static_cast(group_index_returned_) != index) { + XmlElement * group_element = raw_xml_->FirstNamed(QN_ROSTER_GROUP); + size_t group_index = 0; + while(group_index < index) { + ASSERT(group_element != NULL); + group_index++; + group_element = group_element->NextNamed(QN_ROSTER_GROUP); + } + + XmppRosterContactImpl * me = const_cast(this); + me->group_index_returned_ = static_cast(group_index); + me->group_returned_ = group_element; + } + + return group_returned_->BodyText(); +} + +XmppReturnStatus +XmppRosterContactImpl::AddGroup(const std::string& group) { + if (group == STR_EMPTY) + return XMPP_RETURN_BADARGUMENT; + + if (!raw_xml_) + CreateRawXmlSkeleton(); + + if (FindGroup(group, NULL, NULL)) + return XMPP_RETURN_OK; + + raw_xml_->AddElement(new XmlElement(QN_ROSTER_GROUP)); + raw_xml_->AddText(group, 1); + ++group_count_; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppRosterContactImpl::RemoveGroup(const std::string& group) { + if (group == STR_EMPTY) + return XMPP_RETURN_BADARGUMENT; + + if (!raw_xml_) + return XMPP_RETURN_OK; + + XmlChild * child_before; + if (FindGroup(group, NULL, &child_before)) { + raw_xml_->RemoveChildAfter(child_before); + ResetGroupCache(); + } + return XMPP_RETURN_OK; +} + +bool +XmppRosterContactImpl::FindGroup(const std::string& group, + XmlElement** element, + XmlChild** child_before) { + XmlChild * prev_child = NULL; + XmlChild * next_child; + XmlChild * child; + for (child = raw_xml_->FirstChild(); child; child = next_child) { + next_child = child->NextChild(); + if (!child->IsText() && + child->AsElement()->Name() == QN_ROSTER_GROUP && + child->AsElement()->BodyText() == group) { + if (element) + *element = child->AsElement(); + if (child_before) + *child_before = prev_child; + return true; + } + prev_child = child; + } + + return false; +} + +const XmlElement* +XmppRosterContactImpl::raw_xml() const { + if (!raw_xml_) + const_cast(this)->CreateRawXmlSkeleton(); + return raw_xml_.get(); +} + +XmppReturnStatus +XmppRosterContactImpl::set_raw_xml(const XmlElement* xml) { + if (!xml || + xml->Name() != QN_ROSTER_ITEM || + xml->HasAttr(QN_SUBSCRIPTION) || + xml->HasAttr(QN_ASK)) + return XMPP_RETURN_BADARGUMENT; + + ResetGroupCache(); + + raw_xml_.reset(new XmlElement(*xml)); + + return XMPP_RETURN_OK; +} + +void +XmppRosterContactImpl::CreateRawXmlSkeleton() { + raw_xml_.reset(new XmlElement(QN_ROSTER_ITEM)); +} + +// XmppRosterModuleImpl -------------------------------------------------------- +XmppRosterModule * +XmppRosterModule::Create() { + return new XmppRosterModuleImpl(); +} + +XmppRosterModuleImpl::XmppRosterModuleImpl() : + roster_handler_(NULL), + incoming_presence_map_(new JidPresenceVectorMap()), + incoming_presence_vector_(new PresenceVector()), + contacts_(new ContactVector()) { + +} + +XmppRosterModuleImpl::~XmppRosterModuleImpl() { + DeleteIncomingPresence(); + DeleteContacts(); +} + +XmppReturnStatus +XmppRosterModuleImpl::set_roster_handler(XmppRosterHandler * handler) { + roster_handler_ = handler; + return XMPP_RETURN_OK; +} + +XmppRosterHandler* +XmppRosterModuleImpl::roster_handler() { + return roster_handler_; +} + +XmppPresence* +XmppRosterModuleImpl::outgoing_presence() { + return &outgoing_presence_; +} + +XmppReturnStatus +XmppRosterModuleImpl::BroadcastPresence() { + // Scrub the outgoing presence + const XmlElement* element = outgoing_presence_.raw_xml(); + + ASSERT(!element->HasAttr(QN_TO) && + !element->HasAttr(QN_FROM) && + (element->Attr(QN_TYPE) == STR_EMPTY || + element->Attr(QN_TYPE) == "unavailable")); + + if (!engine()) + return XMPP_RETURN_BADSTATE; + + return engine()->SendStanza(element); +} + +XmppReturnStatus +XmppRosterModuleImpl::SendDirectedPresence(const XmppPresence* presence, + const Jid& to_jid) { + if (!presence) + return XMPP_RETURN_BADARGUMENT; + + if (!engine()) + return XMPP_RETURN_BADSTATE; + + XmlElement element(*(presence->raw_xml())); + + if (element.Name() != QN_PRESENCE || + element.HasAttr(QN_TO) || + element.HasAttr(QN_FROM)) + return XMPP_RETURN_BADARGUMENT; + + if (element.HasAttr(QN_TYPE)) { + if (element.Attr(QN_TYPE) != STR_EMPTY && + element.Attr(QN_TYPE) != "unavailable") { + return XMPP_RETURN_BADARGUMENT; + } + } + + element.SetAttr(QN_TO, to_jid.Str()); + + return engine()->SendStanza(&element); +} + +size_t +XmppRosterModuleImpl::GetIncomingPresenceCount() { + return incoming_presence_vector_->size(); +} + +const XmppPresence* +XmppRosterModuleImpl::GetIncomingPresence(size_t index) { + if (index >= incoming_presence_vector_->size()) + return NULL; + return (*incoming_presence_vector_)[index]; +} + +size_t +XmppRosterModuleImpl::GetIncomingPresenceForJidCount(const Jid& jid) +{ + // find the vector in the map + JidPresenceVectorMap::iterator pos; + pos = incoming_presence_map_->find(jid); + if (pos == incoming_presence_map_->end()) + return 0; + + ASSERT(pos->second != NULL); + + return pos->second->size(); +} + +const XmppPresence* +XmppRosterModuleImpl::GetIncomingPresenceForJid(const Jid& jid, + size_t index) { + JidPresenceVectorMap::iterator pos; + pos = incoming_presence_map_->find(jid); + if (pos == incoming_presence_map_->end()) + return NULL; + + ASSERT(pos->second != NULL); + + if (index >= pos->second->size()) + return NULL; + + return (*pos->second)[index]; +} + +XmppReturnStatus +XmppRosterModuleImpl::RequestRosterUpdate() { + if (!engine()) + return XMPP_RETURN_BADSTATE; + + XmlElement roster_get(QN_IQ); + roster_get.AddAttr(QN_TYPE, "get"); + roster_get.AddAttr(QN_ID, engine()->NextId()); + roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); + return engine()->SendIq(&roster_get, this, NULL); +} + +size_t +XmppRosterModuleImpl::GetRosterContactCount() { + return contacts_->size(); +} + +const XmppRosterContact* +XmppRosterModuleImpl::GetRosterContact(size_t index) { + if (index >= contacts_->size()) + return NULL; + return (*contacts_)[index]; +} + +class RosterPredicate { +public: + explicit RosterPredicate(const Jid& jid) : jid_(jid) { + } + + bool operator() (XmppRosterContactImpl *& contact) { + return contact->jid() == jid_; + } + +private: + Jid jid_; +}; + +const XmppRosterContact* +XmppRosterModuleImpl::FindRosterContact(const Jid& jid) { + ContactVector::iterator pos; + + pos = std::find_if(contacts_->begin(), + contacts_->end(), + RosterPredicate(jid)); + if (pos == contacts_->end()) + return NULL; + + return *pos; +} + +XmppReturnStatus +XmppRosterModuleImpl::RequestRosterChange( + const XmppRosterContact* contact) { + if (!contact) + return XMPP_RETURN_BADARGUMENT; + + Jid jid = contact->jid(); + + if (!jid.IsValid()) + return XMPP_RETURN_BADARGUMENT; + + if (!engine()) + return XMPP_RETURN_BADSTATE; + + const XmlElement* contact_xml = contact->raw_xml(); + if (contact_xml->Name() != QN_ROSTER_ITEM || + contact_xml->HasAttr(QN_SUBSCRIPTION) || + contact_xml->HasAttr(QN_ASK)) + return XMPP_RETURN_BADARGUMENT; + + XmlElement roster_add(QN_IQ); + roster_add.AddAttr(QN_TYPE, "set"); + roster_add.AddAttr(QN_ID, engine()->NextId()); + roster_add.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); + roster_add.AddElement(new XmlElement(*contact_xml), 1); + + return engine()->SendIq(&roster_add, this, NULL); +} + +XmppReturnStatus +XmppRosterModuleImpl::RequestRosterRemove(const Jid& jid) { + if (!jid.IsValid()) + return XMPP_RETURN_BADARGUMENT; + + if (!engine()) + return XMPP_RETURN_BADSTATE; + + XmlElement roster_add(QN_IQ); + roster_add.AddAttr(QN_TYPE, "set"); + roster_add.AddAttr(QN_ID, engine()->NextId()); + roster_add.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); + roster_add.AddAttr(QN_JID, jid.Str(), 1); + roster_add.AddAttr(QN_SUBSCRIPTION, "remove", 1); + + return engine()->SendIq(&roster_add, this, NULL); +} + +XmppReturnStatus +XmppRosterModuleImpl::RequestSubscription(const Jid& jid) { + return SendSubscriptionRequest(jid, "subscribe"); +} + +XmppReturnStatus +XmppRosterModuleImpl::CancelSubscription(const Jid& jid) { + return SendSubscriptionRequest(jid, "unsubscribe"); +} + +XmppReturnStatus +XmppRosterModuleImpl::ApproveSubscriber(const Jid& jid) { + return SendSubscriptionRequest(jid, "subscribed"); +} + +XmppReturnStatus +XmppRosterModuleImpl::CancelSubscriber(const Jid& jid) { + return SendSubscriptionRequest(jid, "unsubscribed"); +} + +void +XmppRosterModuleImpl::IqResponse(XmppIqCookie, const XmlElement * stanza) { + // The only real Iq response that we expect to recieve are initial roster + // population + if (stanza->Attr(QN_TYPE) == "error") + { + if (roster_handler_) + roster_handler_->RosterError(this, stanza); + + return; + } + + ASSERT(stanza->Attr(QN_TYPE) == "result"); + + InternalRosterItems(stanza); +} + +bool +XmppRosterModuleImpl::HandleStanza(const XmlElement * stanza) +{ + ASSERT(engine() != NULL); + + // There are two types of stanzas that we care about: presence and roster push + // Iqs + if (stanza->Name() == QN_PRESENCE) { + const std::string& jid_string = stanza->Attr(QN_FROM); + Jid jid(jid_string); + + if (!jid.IsValid()) + return false; // if the Jid isn't valid, don't process + + const std::string& type = stanza->Attr(QN_TYPE); + XmppSubscriptionRequestType request_type; + if (StringToSubscriptionRequestType(type, &request_type)) + InternalSubscriptionRequest(jid, stanza, request_type); + else if (type == "unavailable" || type == STR_EMPTY) + InternalIncomingPresence(jid, stanza); + else if (type == "error") + InternalIncomingPresenceError(jid, stanza); + else + return false; + + return true; + } else if (stanza->Name() == QN_IQ) { + const XmlElement * roster_query = stanza->FirstNamed(QN_ROSTER_QUERY); + if (!roster_query || stanza->Attr(QN_TYPE) != "set") + return false; + + InternalRosterItems(stanza); + + // respond to the IQ + XmlElement result(QN_IQ); + result.AddAttr(QN_TYPE, "result"); + result.AddAttr(QN_TO, stanza->Attr(QN_FROM)); + result.AddAttr(QN_ID, stanza->Attr(QN_ID)); + + engine()->SendStanza(&result); + return true; + } + + return false; +} + +void +XmppRosterModuleImpl::DeleteIncomingPresence() { + // Clear out the vector of all presence notifications + { + PresenceVector::iterator pos; + for (pos = incoming_presence_vector_->begin(); + pos < incoming_presence_vector_->end(); + ++pos) { + XmppPresenceImpl * presence = *pos; + *pos = NULL; + delete presence; + } + incoming_presence_vector_->clear(); + } + + // Clear out all of the small presence vectors per Jid + { + JidPresenceVectorMap::iterator pos; + for (pos = incoming_presence_map_->begin(); + pos != incoming_presence_map_->end(); + ++pos) { + PresenceVector* presence_vector = pos->second; + pos->second = NULL; + delete presence_vector; + } + incoming_presence_map_->clear(); + } +} + +void +XmppRosterModuleImpl::DeleteContacts() { + ContactVector::iterator pos; + for (pos = contacts_->begin(); + pos < contacts_->end(); + ++pos) { + XmppRosterContact* contact = *pos; + *pos = NULL; + delete contact; + } + contacts_->clear(); +} + +XmppReturnStatus +XmppRosterModuleImpl::SendSubscriptionRequest(const Jid& jid, + const std::string& type) { + if (!jid.IsValid()) + return XMPP_RETURN_BADARGUMENT; + + if (!engine()) + return XMPP_RETURN_BADSTATE; + + XmlElement presence_request(QN_PRESENCE); + presence_request.AddAttr(QN_TO, jid.Str()); + presence_request.AddAttr(QN_TYPE, type); + + return engine()->SendStanza(&presence_request); +} + + +void +XmppRosterModuleImpl::InternalSubscriptionRequest(const Jid& jid, + const XmlElement* stanza, + XmppSubscriptionRequestType + request_type) { + if (roster_handler_) + roster_handler_->SubscriptionRequest(this, jid, request_type, stanza); +} + +class PresencePredicate { +public: + explicit PresencePredicate(const Jid& jid) : jid_(jid) { + } + + bool operator() (XmppPresenceImpl *& contact) { + return contact->jid() == jid_; + } + +private: + Jid jid_; +}; + +void +XmppRosterModuleImpl::InternalIncomingPresence(const Jid& jid, + const XmlElement* stanza) { + bool added = false; + Jid bare_jid = jid.BareJid(); + + // First add the presence to the map + JidPresenceVectorMap::iterator pos; + pos = incoming_presence_map_->find(jid.BareJid()); + if (pos == incoming_presence_map_->end()) { + // Insert a new entry into the map. Get the position of this new entry + pos = (incoming_presence_map_->insert( + std::make_pair(bare_jid, new PresenceVector()))).first; + } + + PresenceVector * presence_vector = pos->second; + ASSERT(presence_vector != NULL); + + // Try to find this jid in the bare jid bucket + PresenceVector::iterator presence_pos; + XmppPresenceImpl* presence; + presence_pos = std::find_if(presence_vector->begin(), + presence_vector->end(), + PresencePredicate(jid)); + + // Update/add it to the bucket + if (presence_pos == presence_vector->end()) { + presence = new XmppPresenceImpl(); + if (XMPP_RETURN_OK == presence->set_raw_xml(stanza)) { + added = true; + presence_vector->push_back(presence); + } else { + delete presence; + presence = NULL; + } + } else { + presence = *presence_pos; + presence->set_raw_xml(stanza); + } + + // now add to the comprehensive vector + if (added) + incoming_presence_vector_->push_back(presence); + + // Call back to the user with the changed presence information + if (roster_handler_) + roster_handler_->IncomingPresenceChanged(this, presence); +} + + +void +XmppRosterModuleImpl::InternalIncomingPresenceError(const Jid& jid, + const XmlElement* stanza) { + if (roster_handler_) + roster_handler_->SubscriptionError(this, jid, stanza); +} + +void +XmppRosterModuleImpl::InternalRosterItems(const XmlElement* stanza) { + const XmlElement* result_data = stanza->FirstNamed(QN_ROSTER_QUERY); + if (!result_data) + return; // unknown stuff in result! + + bool all_new = contacts_->empty(); + + for (const XmlElement* roster_item = result_data->FirstNamed(QN_ROSTER_ITEM); + roster_item; + roster_item = roster_item->NextNamed(QN_ROSTER_ITEM)) + { + const std::string& jid_string = roster_item->Attr(QN_JID); + Jid jid(jid_string); + if (!jid.IsValid()) + continue; + + // This algorithm is N^2 on the number of incoming contacts after the + // initial load. There is no way to do this faster without allowing + // duplicates, introducing more data structures or write a custom data + // structure. We'll see if this becomes a perf problem and fix it if it + // does. + ContactVector::iterator pos = contacts_->end(); + + if (!all_new) { + pos = std::find_if(contacts_->begin(), + contacts_->end(), + RosterPredicate(jid)); + } + + if (pos != contacts_->end()) { // Update/remove a current contact + if (roster_item->Attr(QN_SUBSCRIPTION) == "remove") { + XmppRosterContact* contact = *pos; + contacts_->erase(pos); + if (roster_handler_) + roster_handler_->ContactRemoved(this, contact, + std::distance(contacts_->begin(), pos)); + delete contact; + } else { + XmppRosterContact* old_contact = *pos; + *pos = new XmppRosterContactImpl(); + (*pos)->SetXmlFromWire(roster_item); + if (roster_handler_) + roster_handler_->ContactChanged(this, old_contact, + std::distance(contacts_->begin(), pos)); + delete old_contact; + } + } else { // Add a new contact + XmppRosterContactImpl* contact = new XmppRosterContactImpl(); + contact->SetXmlFromWire(roster_item); + contacts_->push_back(contact); + if (roster_handler_ && !all_new) + roster_handler_->ContactsAdded(this, contacts_->size() - 1, 1); + } + } + + // Send a consolidated update if all contacts are new + if (roster_handler_ && all_new) + roster_handler_->ContactsAdded(this, 0, contacts_->size()); +} + +} diff --git a/webrtc/libjingle/xmpp/rostermoduleimpl.h b/webrtc/libjingle/xmpp/rostermoduleimpl.h new file mode 100644 index 0000000000..772692bd65 --- /dev/null +++ b/webrtc/libjingle/xmpp/rostermoduleimpl.h @@ -0,0 +1,287 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ + +#include + +#include "webrtc/libjingle/xmpp/moduleimpl.h" +#include "webrtc/libjingle/xmpp/rostermodule.h" + +namespace buzz { + +//! Presence Information +//! This class stores both presence information for outgoing presence and is +//! returned by methods in XmppRosterModule to represent received incoming +//! presence information. When this class is writeable (non-const) then each +//! update to any property will set the inner xml. Setting the raw_xml will +//! rederive all of the other properties. +class XmppPresenceImpl : public XmppPresence { +public: + virtual ~XmppPresenceImpl() {} + + //! The from Jid of for the presence information. + //! Typically this will be a full Jid with resource specified. For outgoing + //! presence this should remain JID_NULL and will be scrubbed from the + //! stanza when being sent. + virtual const Jid jid() const; + + //! Is the contact available? + virtual XmppPresenceAvailable available() const; + + //! Sets if the user is available or not + virtual XmppReturnStatus set_available(XmppPresenceAvailable available); + + //! The show value of the presence info + virtual XmppPresenceShow presence_show() const; + + //! Set the presence show value + virtual XmppReturnStatus set_presence_show(XmppPresenceShow show); + + //! The Priority of the presence info + virtual int priority() const; + + //! Set the priority of the presence + virtual XmppReturnStatus set_priority(int priority); + + //! The plain text status of the presence info. + //! If there are multiple status because of language, this will either be a + //! status that is not tagged for language or the first available + virtual const std::string status() const; + + //! Sets the status for the presence info. + //! If there is more than one status present already then this will remove + //! them all and replace it with one status element we no specified language + virtual XmppReturnStatus set_status(const std::string& status); + + //! The connection status + virtual XmppPresenceConnectionStatus connection_status() const; + + //! The focus obfuscated GAIA id + virtual const std::string google_user_id() const; + + //! The nickname in the presence + virtual const std::string nickname() const; + + //! The raw xml of the presence update + virtual const XmlElement* raw_xml() const; + + //! Sets the raw presence stanza for the presence update + //! This will cause all other data items in this structure to be rederived + virtual XmppReturnStatus set_raw_xml(const XmlElement * xml); + +private: + XmppPresenceImpl(); + + friend class XmppPresence; + friend class XmppRosterModuleImpl; + + void CreateRawXmlSkeleton(); + + // Store everything in the XML element. If this becomes a perf issue we can + // cache the data. + std::unique_ptr raw_xml_; +}; + +//! A contact as given by the server +class XmppRosterContactImpl : public XmppRosterContact { +public: + virtual ~XmppRosterContactImpl() {} + + //! The jid for the contact. + //! Typically this will be a bare Jid. + virtual const Jid jid() const; + + //! Sets the jid for the roster contact update + virtual XmppReturnStatus set_jid(const Jid& jid); + + //! The name (nickname) stored for this contact + virtual const std::string name() const; + + //! Sets the name + virtual XmppReturnStatus set_name(const std::string& name); + + //! The Presence subscription state stored on the server for this contact + //! This is never settable and will be ignored when generating a roster + //! add/update request + virtual XmppSubscriptionState subscription_state() const; + + //! The number of Groups applied to this contact + virtual size_t GetGroupCount() const; + + //! Gets a Group applied to the contact based on index. + virtual const std::string GetGroup(size_t index) const; + + //! Adds a group to this contact. + //! This will return a no error if the group is already present. + virtual XmppReturnStatus AddGroup(const std::string& group); + + //! Removes a group from the contact. + //! This will return no error if the group isn't there + virtual XmppReturnStatus RemoveGroup(const std::string& group); + + //! The raw xml for this roster contact + virtual const XmlElement* raw_xml() const; + + //! Sets the raw presence stanza for the presence update + //! This will cause all other data items in this structure to be rederived + virtual XmppReturnStatus set_raw_xml(const XmlElement * xml); + +private: + XmppRosterContactImpl(); + + void CreateRawXmlSkeleton(); + void SetXmlFromWire(const XmlElement * xml); + void ResetGroupCache(); + + bool FindGroup(const std::string& group, + XmlElement** element, + XmlChild** child_before); + + + friend class XmppRosterContact; + friend class XmppRosterModuleImpl; + + int group_count_; + int group_index_returned_; + XmlElement * group_returned_; + std::unique_ptr raw_xml_; +}; + +//! An XmppModule for handle roster and presence functionality +class XmppRosterModuleImpl : public XmppModuleImpl, + public XmppRosterModule, public XmppIqHandler { +public: + virtual ~XmppRosterModuleImpl(); + + IMPLEMENT_XMPPMODULE + + //! Sets the roster handler (callbacks) for the module + virtual XmppReturnStatus set_roster_handler(XmppRosterHandler * handler); + + //! Gets the roster handler for the module + virtual XmppRosterHandler* roster_handler(); + + // USER PRESENCE STATE ------------------------------------------------------- + + //! Gets the aggregate outgoing presence + //! This object is non-const and be edited directly. No update is sent + //! to the server until a Broadcast is sent + virtual XmppPresence* outgoing_presence(); + + //! Broadcasts that the user is available. + //! Nothing with respect to presence is sent until this is called. + virtual XmppReturnStatus BroadcastPresence(); + + //! Sends a directed presence to a Jid + //! Note that the client doesn't store where directed presence notifications + //! have been sent. The server can keep the appropriate state + virtual XmppReturnStatus SendDirectedPresence(const XmppPresence* presence, + const Jid& to_jid); + + // INCOMING PRESENCE STATUS -------------------------------------------------- + + //! Returns the number of incoming presence data recorded + virtual size_t GetIncomingPresenceCount(); + + //! Returns an incoming presence datum based on index + virtual const XmppPresence* GetIncomingPresence(size_t index); + + //! Gets the number of presence data for a bare Jid + //! There may be a datum per resource + virtual size_t GetIncomingPresenceForJidCount(const Jid& jid); + + //! Returns a single presence data for a Jid based on index + virtual const XmppPresence* GetIncomingPresenceForJid(const Jid& jid, + size_t index); + + // ROSTER MANAGEMENT --------------------------------------------------------- + + //! Requests an update of the roster from the server + //! This must be called to initialize the client side cache of the roster + //! After this is sent the server should keep this module apprised of any + //! changes. + virtual XmppReturnStatus RequestRosterUpdate(); + + //! Returns the number of contacts in the roster + virtual size_t GetRosterContactCount(); + + //! Returns a contact by index + virtual const XmppRosterContact* GetRosterContact(size_t index); + + //! Finds a contact by Jid + virtual const XmppRosterContact* FindRosterContact(const Jid& jid); + + //! Send a request to the server to add a contact + //! Note that the contact won't show up in the roster until the server can + //! respond. This happens async when the socket is being serviced + virtual XmppReturnStatus RequestRosterChange( + const XmppRosterContact* contact); + + //! Request that the server remove a contact + //! The jabber protocol specifies that the server should also cancel any + //! subscriptions when this is done. Like adding, this contact won't be + //! removed until the server responds. + virtual XmppReturnStatus RequestRosterRemove(const Jid& jid); + + // SUBSCRIPTION MANAGEMENT --------------------------------------------------- + + //! Request a subscription to presence notifications form a Jid + virtual XmppReturnStatus RequestSubscription(const Jid& jid); + + //! Cancel a subscription to presence notifications from a Jid + virtual XmppReturnStatus CancelSubscription(const Jid& jid); + + //! Approve a request to deliver presence notifications to a jid + virtual XmppReturnStatus ApproveSubscriber(const Jid& jid); + + //! Deny or cancel presence notification deliver to a jid + virtual XmppReturnStatus CancelSubscriber(const Jid& jid); + + // XmppIqHandler IMPLEMENTATION ---------------------------------------------- + virtual void IqResponse(XmppIqCookie cookie, const XmlElement * stanza); + +protected: + // XmppModuleImpl OVERRIDES -------------------------------------------------- + virtual bool HandleStanza(const XmlElement *); + + // PRIVATE DATA -------------------------------------------------------------- +private: + friend class XmppRosterModule; + XmppRosterModuleImpl(); + + // Helper functions + void DeleteIncomingPresence(); + void DeleteContacts(); + XmppReturnStatus SendSubscriptionRequest(const Jid& jid, + const std::string& type); + void InternalSubscriptionRequest(const Jid& jid, const XmlElement* stanza, + XmppSubscriptionRequestType request_type); + void InternalIncomingPresence(const Jid& jid, const XmlElement* stanza); + void InternalIncomingPresenceError(const Jid& jid, const XmlElement* stanza); + void InternalRosterItems(const XmlElement* stanza); + + // Member data + XmppPresenceImpl outgoing_presence_; + XmppRosterHandler* roster_handler_; + + typedef std::vector PresenceVector; + typedef std::map JidPresenceVectorMap; + std::unique_ptr incoming_presence_map_; + std::unique_ptr incoming_presence_vector_; + + typedef std::vector ContactVector; + std::unique_ptr contacts_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ diff --git a/webrtc/libjingle/xmpp/saslcookiemechanism.h b/webrtc/libjingle/xmpp/saslcookiemechanism.h new file mode 100644 index 0000000000..7a912466a6 --- /dev/null +++ b/webrtc/libjingle/xmpp/saslcookiemechanism.h @@ -0,0 +1,69 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_SASLCOOKIEMECHANISM_H_ +#define WEBRTC_LIBJINGLE_XMPP_SASLCOOKIEMECHANISM_H_ + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslmechanism.h" + +namespace buzz { + +class SaslCookieMechanism : public SaslMechanism { + +public: + SaslCookieMechanism(const std::string & mechanism, + const std::string & username, + const std::string & cookie, + const std::string & token_service) + : mechanism_(mechanism), + username_(username), + cookie_(cookie), + token_service_(token_service) {} + + SaslCookieMechanism(const std::string & mechanism, + const std::string & username, + const std::string & cookie) + : mechanism_(mechanism), + username_(username), + cookie_(cookie), + token_service_("") {} + + virtual std::string GetMechanismName() { return mechanism_; } + + virtual XmlElement * StartSaslAuth() { + // send initial request + XmlElement * el = new XmlElement(QN_SASL_AUTH, true); + el->AddAttr(QN_MECHANISM, mechanism_); + if (!token_service_.empty()) { + el->AddAttr(QN_GOOGLE_AUTH_SERVICE, token_service_); + } + + std::string credential; + credential.append("\0", 1); + credential.append(username_); + credential.append("\0", 1); + credential.append(cookie_); + el->AddText(Base64Encode(credential)); + return el; + } + +private: + std::string mechanism_; + std::string username_; + std::string cookie_; + std::string token_service_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_SASLCOOKIEMECHANISM_H_ diff --git a/webrtc/libjingle/xmpp/saslhandler.h b/webrtc/libjingle/xmpp/saslhandler.h new file mode 100644 index 0000000000..f2e3844405 --- /dev/null +++ b/webrtc/libjingle/xmpp/saslhandler.h @@ -0,0 +1,42 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_SASLHANDLER_H_ +#define WEBRTC_LIBJINGLE_XMPP_SASLHANDLER_H_ + +#include +#include + +namespace buzz { + +class XmlElement; +class SaslMechanism; + +// Creates mechanisms to deal with a given mechanism +class SaslHandler { + +public: + + // Intended to be subclassed + virtual ~SaslHandler() {} + + // Should pick the best method according to this handler + // returns the empty string if none are suitable + virtual std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted) = 0; + + // Creates a SaslMechanism for the given mechanism name (you own it + // once you get it). + // If not handled, return NULL. + virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) = 0; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_SASLHANDLER_H_ diff --git a/webrtc/libjingle/xmpp/saslmechanism.cc b/webrtc/libjingle/xmpp/saslmechanism.cc new file mode 100644 index 0000000000..b4d6e9baa1 --- /dev/null +++ b/webrtc/libjingle/xmpp/saslmechanism.cc @@ -0,0 +1,55 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslmechanism.h" +#include "webrtc/base/base64.h" + +using rtc::Base64; + +namespace buzz { + +XmlElement * +SaslMechanism::StartSaslAuth() { + return new XmlElement(QN_SASL_AUTH, true); +} + +XmlElement * +SaslMechanism::HandleSaslChallenge(const XmlElement * challenge) { + return new XmlElement(QN_SASL_ABORT, true); +} + +void +SaslMechanism::HandleSaslSuccess(const XmlElement * success) { +} + +void +SaslMechanism::HandleSaslFailure(const XmlElement * failure) { +} + +std::string +SaslMechanism::Base64Encode(const std::string & plain) { + return Base64::Encode(plain); +} + +std::string +SaslMechanism::Base64Decode(const std::string & encoded) { + return Base64::Decode(encoded, Base64::DO_LAX); +} + +std::string +SaslMechanism::Base64EncodeFromArray(const char * plain, size_t length) { + std::string result; + Base64::EncodeFromArray(plain, length, &result); + return result; +} + +} diff --git a/webrtc/libjingle/xmpp/saslmechanism.h b/webrtc/libjingle/xmpp/saslmechanism.h new file mode 100644 index 0000000000..9c392e5667 --- /dev/null +++ b/webrtc/libjingle/xmpp/saslmechanism.h @@ -0,0 +1,57 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_SASLMECHANISM_H_ +#define WEBRTC_LIBJINGLE_XMPP_SASLMECHANISM_H_ + +#include + +namespace buzz { + +class XmlElement; + + +// Defines a mechnanism to do SASL authentication. +// Subclass instances should have a self-contained way to present +// credentials. +class SaslMechanism { + +public: + + // Intended to be subclassed + virtual ~SaslMechanism() {} + + // Should return the name of the SASL mechanism, e.g., "PLAIN" + virtual std::string GetMechanismName() = 0; + + // Should generate the initial "auth" request. Default is just . + virtual XmlElement * StartSaslAuth(); + + // Should respond to a SASL "" request. Default is + // to abort (for mechanisms that do not do challenge-response) + virtual XmlElement * HandleSaslChallenge(const XmlElement * challenge); + + // Notification of a SASL "". Sometimes information + // is passed on success. + virtual void HandleSaslSuccess(const XmlElement * success); + + // Notification of a SASL "". Sometimes information + // for the user is passed on failure. + virtual void HandleSaslFailure(const XmlElement * failure); + +protected: + static std::string Base64Encode(const std::string & plain); + static std::string Base64Decode(const std::string & encoded); + static std::string Base64EncodeFromArray(const char * plain, size_t length); +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_SASLMECHANISM_H_ diff --git a/webrtc/libjingle/xmpp/saslplainmechanism.h b/webrtc/libjingle/xmpp/saslplainmechanism.h new file mode 100644 index 0000000000..8e162e25b7 --- /dev/null +++ b/webrtc/libjingle/xmpp/saslplainmechanism.h @@ -0,0 +1,48 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_SASLPLAINMECHANISM_H_ +#define WEBRTC_LIBJINGLE_XMPP_SASLPLAINMECHANISM_H_ + +#include "webrtc/libjingle/xmpp/saslmechanism.h" +#include "webrtc/base/cryptstring.h" + +namespace buzz { + +class SaslPlainMechanism : public SaslMechanism { + +public: + SaslPlainMechanism(const buzz::Jid user_jid, const rtc::CryptString & password) : + user_jid_(user_jid), password_(password) {} + + virtual std::string GetMechanismName() { return "PLAIN"; } + + virtual XmlElement * StartSaslAuth() { + // send initial request + XmlElement * el = new XmlElement(QN_SASL_AUTH, true); + el->AddAttr(QN_MECHANISM, "PLAIN"); + + rtc::FormatCryptString credential; + credential.Append("\0", 1); + credential.Append(user_jid_.node()); + credential.Append("\0", 1); + credential.Append(&password_); + el->AddText(Base64EncodeFromArray(credential.GetData(), credential.GetLength())); + return el; + } + +private: + Jid user_jid_; + rtc::CryptString password_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_SASLPLAINMECHANISM_H_ diff --git a/webrtc/libjingle/xmpp/util_unittest.cc b/webrtc/libjingle/xmpp/util_unittest.cc new file mode 100644 index 0000000000..330ab48ef4 --- /dev/null +++ b/webrtc/libjingle/xmpp/util_unittest.cc @@ -0,0 +1,109 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/gunit.h" + +namespace buzz { + +void XmppTestHandler::WriteOutput(const char * bytes, size_t len) { + output_ << std::string(bytes, len); +} + +void XmppTestHandler::StartTls(const std::string & cname) { + output_ << "[START-TLS " << cname << "]"; +} + +void XmppTestHandler::CloseConnection() { + output_ << "[CLOSED]"; +} + +void XmppTestHandler::OnStateChange(int state) { + switch (static_cast(state)) { + case XmppEngine::STATE_START: + session_ << "[START]"; + break; + case XmppEngine::STATE_OPENING: + session_ << "[OPENING]"; + break; + case XmppEngine::STATE_OPEN: + session_ << "[OPEN]"; + break; + case XmppEngine::STATE_CLOSED: + session_ << "[CLOSED]"; + switch (engine_->GetError(NULL)) { + case XmppEngine::ERROR_NONE: + // do nothing + break; + case XmppEngine::ERROR_XML: + session_ << "[ERROR-XML]"; + break; + case XmppEngine::ERROR_STREAM: + session_ << "[ERROR-STREAM]"; + break; + case XmppEngine::ERROR_VERSION: + session_ << "[ERROR-VERSION]"; + break; + case XmppEngine::ERROR_UNAUTHORIZED: + session_ << "[ERROR-UNAUTHORIZED]"; + break; + case XmppEngine::ERROR_TLS: + session_ << "[ERROR-TLS]"; + break; + case XmppEngine::ERROR_AUTH: + session_ << "[ERROR-AUTH]"; + break; + case XmppEngine::ERROR_BIND: + session_ << "[ERROR-BIND]"; + break; + case XmppEngine::ERROR_CONNECTION_CLOSED: + session_ << "[ERROR-CONNECTION-CLOSED]"; + break; + case XmppEngine::ERROR_DOCUMENT_CLOSED: + session_ << "[ERROR-DOCUMENT-CLOSED]"; + break; + default: + break; + } + break; + default: + break; + } +} + +bool XmppTestHandler::HandleStanza(const XmlElement * stanza) { + stanza_ << stanza->Str(); + return true; +} + +std::string XmppTestHandler::OutputActivity() { + std::string result = output_.str(); + output_.str(""); + return result; +} + +std::string XmppTestHandler::SessionActivity() { + std::string result = session_.str(); + session_.str(""); + return result; +} + +std::string XmppTestHandler::StanzaActivity() { + std::string result = stanza_.str(); + stanza_.str(""); + return result; +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/util_unittest.h b/webrtc/libjingle/xmpp/util_unittest.h new file mode 100644 index 0000000000..38009fb4e9 --- /dev/null +++ b/webrtc/libjingle/xmpp/util_unittest.h @@ -0,0 +1,58 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_UTIL_UNITTEST_H_ +#define WEBRTC_LIBJINGLE_XMPP_UTIL_UNITTEST_H_ + +#include +#include +#include "webrtc/libjingle/xmpp/xmppengine.h" + +namespace buzz { + +// This class captures callbacks from engine. +class XmppTestHandler : public XmppOutputHandler, public XmppSessionHandler, + public XmppStanzaHandler { + public: + explicit XmppTestHandler(XmppEngine* engine) : engine_(engine) {} + virtual ~XmppTestHandler() {} + + void SetEngine(XmppEngine* engine); + + // Output handler + virtual void WriteOutput(const char * bytes, size_t len); + virtual void StartTls(const std::string & cname); + virtual void CloseConnection(); + + // Session handler + virtual void OnStateChange(int state); + + // Stanza handler + virtual bool HandleStanza(const XmlElement* stanza); + + std::string OutputActivity(); + std::string SessionActivity(); + std::string StanzaActivity(); + + private: + XmppEngine* engine_; + std::stringstream output_; + std::stringstream session_; + std::stringstream stanza_; +}; + +} // namespace buzz + +inline std::ostream& operator<<(std::ostream& os, const buzz::Jid& jid) { + os << jid.Str(); + return os; +} + +#endif // WEBRTC_LIBJINGLE_XMPP_UTIL_UNITTEST_H_ diff --git a/webrtc/libjingle/xmpp/xmppauth.cc b/webrtc/libjingle/xmpp/xmppauth.cc new file mode 100644 index 0000000000..a3d2f67849 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppauth.cc @@ -0,0 +1,88 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmppauth.h" + +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslcookiemechanism.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" + +XmppAuth::XmppAuth() : done_(false) { +} + +XmppAuth::~XmppAuth() { +} + +void XmppAuth::StartPreXmppAuth(const buzz::Jid& jid, + const rtc::SocketAddress& server, + const rtc::CryptString& pass, + const std::string& auth_mechanism, + const std::string& auth_token) { + jid_ = jid; + passwd_ = pass; + auth_mechanism_ = auth_mechanism; + auth_token_ = auth_token; + done_ = true; + + SignalAuthDone(); +} + +static bool contains(const std::vector& strings, + const std::string& string) { + return std::find(strings.begin(), strings.end(), string) != strings.end(); +} + +std::string XmppAuth::ChooseBestSaslMechanism( + const std::vector& mechanisms, + bool encrypted) { + // First try Oauth2. + if (GetAuthMechanism() == buzz::AUTH_MECHANISM_OAUTH2 && + contains(mechanisms, buzz::AUTH_MECHANISM_OAUTH2)) { + return buzz::AUTH_MECHANISM_OAUTH2; + } + + // A token is the weakest auth - 15s, service-limited, so prefer it. + if (GetAuthMechanism() == buzz::AUTH_MECHANISM_GOOGLE_TOKEN && + contains(mechanisms, buzz::AUTH_MECHANISM_GOOGLE_TOKEN)) { + return buzz::AUTH_MECHANISM_GOOGLE_TOKEN; + } + + // A cookie is the next weakest - 14 days. + if (GetAuthMechanism() == buzz::AUTH_MECHANISM_GOOGLE_COOKIE && + contains(mechanisms, buzz::AUTH_MECHANISM_GOOGLE_COOKIE)) { + return buzz::AUTH_MECHANISM_GOOGLE_COOKIE; + } + + // As a last resort, use plain authentication. + if (contains(mechanisms, buzz::AUTH_MECHANISM_PLAIN)) { + return buzz::AUTH_MECHANISM_PLAIN; + } + + // No good mechanism found + return ""; +} + +buzz::SaslMechanism* XmppAuth::CreateSaslMechanism( + const std::string& mechanism) { + if (mechanism == buzz::AUTH_MECHANISM_OAUTH2) { + return new buzz::SaslCookieMechanism( + mechanism, jid_.Str(), auth_token_, "oauth2"); + } else if (mechanism == buzz::AUTH_MECHANISM_GOOGLE_TOKEN) { + return new buzz::SaslCookieMechanism(mechanism, jid_.Str(), auth_token_); + // } else if (mechanism == buzz::AUTH_MECHANISM_GOOGLE_COOKIE) { + // return new buzz::SaslCookieMechanism(mechanism, jid.Str(), sid_); + } else if (mechanism == buzz::AUTH_MECHANISM_PLAIN) { + return new buzz::SaslPlainMechanism(jid_, passwd_); + } else { + return NULL; + } +} diff --git a/webrtc/libjingle/xmpp/xmppauth.h b/webrtc/libjingle/xmpp/xmppauth.h new file mode 100644 index 0000000000..dd363d17ca --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppauth.h @@ -0,0 +1,61 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPAUTH_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPAUTH_H_ + +#include + +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/prexmppauth.h" +#include "webrtc/libjingle/xmpp/saslhandler.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/sigslot.h" + +class XmppAuth: public buzz::PreXmppAuth { +public: + XmppAuth(); + virtual ~XmppAuth(); + + // TODO: Just have one "secret" that is either pass or + // token? + virtual void StartPreXmppAuth(const buzz::Jid& jid, + const rtc::SocketAddress& server, + const rtc::CryptString& pass, + const std::string& auth_mechanism, + const std::string& auth_token); + + virtual bool IsAuthDone() const { return done_; } + virtual bool IsAuthorized() const { return true; } + virtual bool HadError() const { return false; } + virtual int GetError() const { return 0; } + virtual buzz::CaptchaChallenge GetCaptchaChallenge() const { + return buzz::CaptchaChallenge(); + } + virtual std::string GetAuthMechanism() const { return auth_mechanism_; } + virtual std::string GetAuthToken() const { return auth_token_; } + + virtual std::string ChooseBestSaslMechanism( + const std::vector& mechanisms, + bool encrypted); + + virtual buzz::SaslMechanism * CreateSaslMechanism( + const std::string& mechanism); + +private: + buzz::Jid jid_; + rtc::CryptString passwd_; + std::string auth_mechanism_; + std::string auth_token_; + bool done_; +}; + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPAUTH_H_ + diff --git a/webrtc/libjingle/xmpp/xmppclient.cc b/webrtc/libjingle/xmpp/xmppclient.cc new file mode 100644 index 0000000000..a87659824b --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppclient.cc @@ -0,0 +1,423 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmppclient.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/plainsaslhandler.h" +#include "webrtc/libjingle/xmpp/prexmppauth.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/stringutils.h" +#include "xmpptask.h" + +namespace buzz { + +class XmppClient::Private : + public sigslot::has_slots<>, + public XmppSessionHandler, + public XmppOutputHandler { +public: + + explicit Private(XmppClient* client) : + client_(client), + socket_(), + engine_(), + proxy_port_(0), + pre_engine_error_(XmppEngine::ERROR_NONE), + pre_engine_subcode_(0), + signal_closed_(false), + allow_plain_(false) {} + + virtual ~Private() { + // We need to disconnect from socket_ before engine_ is destructed (by + // the auto-generated destructor code). + ResetSocket(); + } + + // the owner + XmppClient* const client_; + + // the two main objects + std::unique_ptr socket_; + std::unique_ptr engine_; + std::unique_ptr pre_auth_; + rtc::CryptString pass_; + std::string auth_mechanism_; + std::string auth_token_; + rtc::SocketAddress server_; + std::string proxy_host_; + int proxy_port_; + XmppEngine::Error pre_engine_error_; + int pre_engine_subcode_; + CaptchaChallenge captcha_challenge_; + bool signal_closed_; + bool allow_plain_; + + void ResetSocket() { + if (socket_) { + socket_->SignalConnected.disconnect(this); + socket_->SignalRead.disconnect(this); + socket_->SignalClosed.disconnect(this); + socket_.reset(NULL); + } + } + + // implementations of interfaces + void OnStateChange(int state); + void WriteOutput(const char* bytes, size_t len); + void StartTls(const std::string& domainname); + void CloseConnection(); + + // slots for socket signals + void OnSocketConnected(); + void OnSocketRead(); + void OnSocketClosed(); +}; + +bool IsTestServer(const std::string& server_name, + const std::string& test_server_domain) { + return (!test_server_domain.empty() && + rtc::ends_with(server_name.c_str(), + test_server_domain.c_str())); +} + +XmppReturnStatus XmppClient::Connect( + const XmppClientSettings& settings, + const std::string& lang, AsyncSocket* socket, PreXmppAuth* pre_auth) { + if (socket == NULL) + return XMPP_RETURN_BADARGUMENT; + if (d_->socket_) + return XMPP_RETURN_BADSTATE; + + d_->socket_.reset(socket); + + d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected); + d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead); + d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed); + + d_->engine_.reset(XmppEngine::Create()); + d_->engine_->SetSessionHandler(d_.get()); + d_->engine_->SetOutputHandler(d_.get()); + if (!settings.resource().empty()) { + d_->engine_->SetRequestedResource(settings.resource()); + } + d_->engine_->SetTls(settings.use_tls()); + + // The talk.google.com server returns a certificate with common-name: + // CN="gmail.com" for @gmail.com accounts, + // CN="googlemail.com" for @googlemail.com accounts, + // CN="talk.google.com" for other accounts (such as @example.com), + // so we tweak the tls server setting for those other accounts to match the + // returned certificate CN of "talk.google.com". + // For other servers, we leave the strings empty, which causes the jid's + // domain to be used. We do the same for gmail.com and googlemail.com as the + // returned CN matches the account domain in those cases. + std::string server_name = settings.server().HostAsURIString(); + if (server_name == buzz::STR_TALK_GOOGLE_COM || + server_name == buzz::STR_TALKX_L_GOOGLE_COM || + server_name == buzz::STR_XMPP_GOOGLE_COM || + server_name == buzz::STR_XMPPX_L_GOOGLE_COM || + IsTestServer(server_name, settings.test_server_domain())) { + if (settings.host() != STR_GMAIL_COM && + settings.host() != STR_GOOGLEMAIL_COM) { + d_->engine_->SetTlsServer("", STR_TALK_GOOGLE_COM); + } + } + + // Set language + d_->engine_->SetLanguage(lang); + + d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY)); + + d_->pass_ = settings.pass(); + d_->auth_mechanism_ = settings.auth_mechanism(); + d_->auth_token_ = settings.auth_token(); + d_->server_ = settings.server(); + d_->proxy_host_ = settings.proxy_host(); + d_->proxy_port_ = settings.proxy_port(); + d_->allow_plain_ = settings.allow_plain(); + d_->pre_auth_.reset(pre_auth); + + return XMPP_RETURN_OK; +} + +XmppEngine::State XmppClient::GetState() const { + if (!d_->engine_) + return XmppEngine::STATE_NONE; + return d_->engine_->GetState(); +} + +XmppEngine::Error XmppClient::GetError(int* subcode) { + if (subcode) { + *subcode = 0; + } + if (!d_->engine_) + return XmppEngine::ERROR_NONE; + if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) { + if (subcode) { + *subcode = d_->pre_engine_subcode_; + } + return d_->pre_engine_error_; + } + return d_->engine_->GetError(subcode); +} + +const XmlElement* XmppClient::GetStreamError() { + if (!d_->engine_) { + return NULL; + } + return d_->engine_->GetStreamError(); +} + +CaptchaChallenge XmppClient::GetCaptchaChallenge() { + if (!d_->engine_) + return CaptchaChallenge(); + return d_->captcha_challenge_; +} + +std::string XmppClient::GetAuthMechanism() { + if (!d_->engine_) + return ""; + return d_->auth_mechanism_; +} + +std::string XmppClient::GetAuthToken() { + if (!d_->engine_) + return ""; + return d_->auth_token_; +} + +int XmppClient::ProcessStart() { + // Should not happen, but was observed in crash reports + if (!d_->socket_) { + LOG(LS_ERROR) << "socket_ already reset"; + return STATE_DONE; + } + + if (d_->pre_auth_) { + d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone); + d_->pre_auth_->StartPreXmppAuth( + d_->engine_->GetUser(), d_->server_, d_->pass_, + d_->auth_mechanism_, d_->auth_token_); + d_->pass_.Clear(); // done with this; + return STATE_PRE_XMPP_LOGIN; + } + else { + d_->engine_->SetSaslHandler(new PlainSaslHandler( + d_->engine_->GetUser(), d_->pass_, d_->allow_plain_)); + d_->pass_.Clear(); // done with this; + return STATE_START_XMPP_LOGIN; + } +} + +void XmppClient::OnAuthDone() { + Wake(); +} + +int XmppClient::ProcessTokenLogin() { + // Should not happen, but was observed in crash reports + if (!d_->socket_) { + LOG(LS_ERROR) << "socket_ already reset"; + return STATE_DONE; + } + + // Don't know how this could happen, but crash reports show it as NULL + if (!d_->pre_auth_) { + d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; + EnsureClosed(); + return STATE_ERROR; + } + + // Wait until pre authentication is done is done + if (!d_->pre_auth_->IsAuthDone()) + return STATE_BLOCKED; + + if (!d_->pre_auth_->IsAuthorized()) { + // maybe split out a case when gaia is down? + if (d_->pre_auth_->HadError()) { + d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; + d_->pre_engine_subcode_ = d_->pre_auth_->GetError(); + } + else { + d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED; + d_->pre_engine_subcode_ = 0; + d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge(); + } + d_->pre_auth_.reset(NULL); // done with this + EnsureClosed(); + return STATE_ERROR; + } + + // Save auth token as a result + + d_->auth_mechanism_ = d_->pre_auth_->GetAuthMechanism(); + d_->auth_token_ = d_->pre_auth_->GetAuthToken(); + + // transfer ownership of pre_auth_ to engine + d_->engine_->SetSaslHandler(d_->pre_auth_.release()); + return STATE_START_XMPP_LOGIN; +} + +int XmppClient::ProcessStartXmppLogin() { + // Should not happen, but was observed in crash reports + if (!d_->socket_) { + LOG(LS_ERROR) << "socket_ already reset"; + return STATE_DONE; + } + + // Done with pre-connect tasks - connect! + if (!d_->socket_->Connect(d_->server_)) { + EnsureClosed(); + return STATE_ERROR; + } + + return STATE_RESPONSE; +} + +int XmppClient::ProcessResponse() { + // Hang around while we are connected. + if (!delivering_signal_ && + (!d_->engine_ || d_->engine_->GetState() == XmppEngine::STATE_CLOSED)) + return STATE_DONE; + return STATE_BLOCKED; +} + +XmppReturnStatus XmppClient::Disconnect() { + if (!d_->socket_) + return XMPP_RETURN_BADSTATE; + Abort(); + d_->engine_->Disconnect(); + d_->ResetSocket(); + return XMPP_RETURN_OK; +} + +XmppClient::XmppClient(TaskParent* parent) + : XmppTaskParentInterface(parent), + delivering_signal_(false), + valid_(false) { + d_.reset(new Private(this)); + valid_ = true; +} + +XmppClient::~XmppClient() { + valid_ = false; +} + +const Jid& XmppClient::jid() const { + return d_->engine_->FullJid(); +} + + +std::string XmppClient::NextId() { + return d_->engine_->NextId(); +} + +XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) { + return d_->engine_->SendStanza(stanza); +} + +XmppReturnStatus XmppClient::SendStanzaError( + const XmlElement* old_stanza, XmppStanzaError xse, + const std::string& message) { + return d_->engine_->SendStanzaError(old_stanza, xse, message); +} + +XmppReturnStatus XmppClient::SendRaw(const std::string& text) { + return d_->engine_->SendRaw(text); +} + +XmppEngine* XmppClient::engine() { + return d_->engine_.get(); +} + +void XmppClient::Private::OnSocketConnected() { + engine_->Connect(); +} + +void XmppClient::Private::OnSocketRead() { + char bytes[4096]; + size_t bytes_read; + for (;;) { + // Should not happen, but was observed in crash reports + if (!socket_) { + LOG(LS_ERROR) << "socket_ already reset"; + return; + } + + if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) { + // TODO: deal with error information + return; + } + + if (bytes_read == 0) + return; + +//#if !defined(NDEBUG) + client_->SignalLogInput(bytes, static_cast(bytes_read)); +//#endif + + engine_->HandleInput(bytes, bytes_read); + } +} + +void XmppClient::Private::OnSocketClosed() { + int code = socket_->GetError(); + engine_->ConnectionClosed(code); +} + +void XmppClient::Private::OnStateChange(int state) { + if (state == XmppEngine::STATE_CLOSED) { + client_->EnsureClosed(); + } + else { + client_->SignalStateChange((XmppEngine::State)state); + } + client_->Wake(); +} + +void XmppClient::Private::WriteOutput(const char* bytes, size_t len) { +//#if !defined(NDEBUG) + client_->SignalLogOutput(bytes, static_cast(len)); +//#endif + + socket_->Write(bytes, len); + // TODO: deal with error information +} + +void XmppClient::Private::StartTls(const std::string& domain) { +#if defined(FEATURE_ENABLE_SSL) + socket_->StartTls(domain); +#endif +} + +void XmppClient::Private::CloseConnection() { + socket_->Close(); +} + +void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) { + d_->engine_->AddStanzaHandler(task, level); +} + +void XmppClient::RemoveXmppTask(XmppTask* task) { + d_->engine_->RemoveStanzaHandler(task); +} + +void XmppClient::EnsureClosed() { + if (!d_->signal_closed_) { + d_->signal_closed_ = true; + delivering_signal_ = true; + SignalStateChange(XmppEngine::STATE_CLOSED); + delivering_signal_ = false; + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/xmppclient.h b/webrtc/libjingle/xmpp/xmppclient.h new file mode 100644 index 0000000000..dba591cb44 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppclient.h @@ -0,0 +1,149 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_ + +#include +#include + +#include "webrtc/libjingle/xmpp/asyncsocket.h" +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/task.h" + +namespace buzz { + +class PreXmppAuth; +class CaptchaChallenge; + +// Just some non-colliding number. Could have picked "1". +#define XMPP_CLIENT_TASK_CODE 0x366c1e47 + +///////////////////////////////////////////////////////////////////// +// +// XMPPCLIENT +// +///////////////////////////////////////////////////////////////////// +// +// See Task first. XmppClient is a parent task for XmppTasks. +// +// XmppClient is a task which is designed to be the parent task for +// all tasks that depend on a single Xmpp connection. If you want to, +// for example, listen for subscription requests forever, then your +// listener should be a task that is a child of the XmppClient that owns +// the connection you are using. XmppClient has all the utility methods +// that basically drill through to XmppEngine. +// +// XmppClient is just a wrapper for XmppEngine, and if I were writing it +// all over again, I would make XmppClient == XmppEngine. Why? +// XmppEngine needs tasks too, for example it has an XmppLoginTask which +// should just be the same kind of Task instead of an XmppEngine specific +// thing. It would help do certain things like GAIA auth cleaner. +// +///////////////////////////////////////////////////////////////////// + +class XmppClient : public XmppTaskParentInterface, + public XmppClientInterface, + public sigslot::has_slots<> +{ +public: + explicit XmppClient(rtc::TaskParent * parent); + virtual ~XmppClient(); + + XmppReturnStatus Connect(const XmppClientSettings & settings, + const std::string & lang, + AsyncSocket * socket, + PreXmppAuth * preauth); + + virtual int ProcessStart(); + virtual int ProcessResponse(); + XmppReturnStatus Disconnect(); + + sigslot::signal1 SignalStateChange; + XmppEngine::Error GetError(int *subcode); + + // When there is a stanza, return the stanza + // so that they can be handled. + const XmlElement *GetStreamError(); + + // When there is an authentication error, we may have captcha info + // that the user can use to unlock their account + CaptchaChallenge GetCaptchaChallenge(); + + // When authentication is successful, this returns the service token + // (if we used GAIA authentication) + std::string GetAuthMechanism(); + std::string GetAuthToken(); + + XmppReturnStatus SendRaw(const std::string & text); + + XmppEngine* engine(); + + sigslot::signal2 SignalLogInput; + sigslot::signal2 SignalLogOutput; + + // As XmppTaskParentIntreface + virtual XmppClientInterface* GetClient() { return this; } + + // As XmppClientInterface + virtual XmppEngine::State GetState() const; + virtual const Jid& jid() const; + virtual std::string NextId(); + virtual XmppReturnStatus SendStanza(const XmlElement *stanza); + virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text); + virtual void AddXmppTask(XmppTask *, XmppEngine::HandlerLevel); + virtual void RemoveXmppTask(XmppTask *); + + private: + friend class XmppTask; + + void OnAuthDone(); + + // Internal state management + enum { + STATE_PRE_XMPP_LOGIN = STATE_NEXT, + STATE_START_XMPP_LOGIN = STATE_NEXT + 1, + }; + int Process(int state) { + switch (state) { + case STATE_PRE_XMPP_LOGIN: return ProcessTokenLogin(); + case STATE_START_XMPP_LOGIN: return ProcessStartXmppLogin(); + default: return Task::Process(state); + } + } + + std::string GetStateName(int state) const { + switch (state) { + case STATE_PRE_XMPP_LOGIN: return "PRE_XMPP_LOGIN"; + case STATE_START_XMPP_LOGIN: return "START_XMPP_LOGIN"; + default: return Task::GetStateName(state); + } + } + + int ProcessTokenLogin(); + int ProcessStartXmppLogin(); + void EnsureClosed(); + + class Private; + friend class Private; + std::unique_ptr d_; + + bool delivering_signal_; + bool valid_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_ diff --git a/webrtc/libjingle/xmpp/xmppclientsettings.h b/webrtc/libjingle/xmpp/xmppclientsettings.h new file mode 100644 index 0000000000..5b7572aa6e --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppclientsettings.h @@ -0,0 +1,111 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPCLIENTSETTINGS_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPCLIENTSETTINGS_H_ + +#include "webrtc/p2p/base/port.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/cryptstring.h" + +namespace buzz { + +class XmppUserSettings { + public: + XmppUserSettings() + : use_tls_(buzz::TLS_DISABLED), + allow_plain_(false) { + } + + void set_user(const std::string& user) { user_ = user; } + void set_host(const std::string& host) { host_ = host; } + void set_pass(const rtc::CryptString& pass) { pass_ = pass; } + void set_auth_token(const std::string& mechanism, + const std::string& token) { + auth_mechanism_ = mechanism; + auth_token_ = token; + } + void set_resource(const std::string& resource) { resource_ = resource; } + void set_use_tls(const TlsOptions use_tls) { use_tls_ = use_tls; } + void set_allow_plain(bool f) { allow_plain_ = f; } + void set_test_server_domain(const std::string& test_server_domain) { + test_server_domain_ = test_server_domain; + } + void set_token_service(const std::string& token_service) { + token_service_ = token_service; + } + + const std::string& user() const { return user_; } + const std::string& host() const { return host_; } + const rtc::CryptString& pass() const { return pass_; } + const std::string& auth_mechanism() const { return auth_mechanism_; } + const std::string& auth_token() const { return auth_token_; } + const std::string& resource() const { return resource_; } + TlsOptions use_tls() const { return use_tls_; } + bool allow_plain() const { return allow_plain_; } + const std::string& test_server_domain() const { return test_server_domain_; } + const std::string& token_service() const { return token_service_; } + + private: + std::string user_; + std::string host_; + rtc::CryptString pass_; + std::string auth_mechanism_; + std::string auth_token_; + std::string resource_; + TlsOptions use_tls_; + bool allow_plain_; + std::string test_server_domain_; + std::string token_service_; +}; + +class XmppClientSettings : public XmppUserSettings { + public: + XmppClientSettings() + : protocol_(cricket::PROTO_TCP), + proxy_(rtc::PROXY_NONE), + proxy_port_(80), + use_proxy_auth_(false) { + } + + void set_server(const rtc::SocketAddress& server) { + server_ = server; + } + void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; } + void set_proxy(rtc::ProxyType f) { proxy_ = f; } + void set_proxy_host(const std::string& host) { proxy_host_ = host; } + void set_proxy_port(int port) { proxy_port_ = port; }; + void set_use_proxy_auth(bool f) { use_proxy_auth_ = f; } + void set_proxy_user(const std::string& user) { proxy_user_ = user; } + void set_proxy_pass(const rtc::CryptString& pass) { proxy_pass_ = pass; } + + const rtc::SocketAddress& server() const { return server_; } + cricket::ProtocolType protocol() const { return protocol_; } + rtc::ProxyType proxy() const { return proxy_; } + const std::string& proxy_host() const { return proxy_host_; } + int proxy_port() const { return proxy_port_; } + bool use_proxy_auth() const { return use_proxy_auth_; } + const std::string& proxy_user() const { return proxy_user_; } + const rtc::CryptString& proxy_pass() const { return proxy_pass_; } + + private: + rtc::SocketAddress server_; + cricket::ProtocolType protocol_; + rtc::ProxyType proxy_; + std::string proxy_host_; + int proxy_port_; + bool use_proxy_auth_; + std::string proxy_user_; + rtc::CryptString proxy_pass_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_ diff --git a/webrtc/libjingle/xmpp/xmppengine.h b/webrtc/libjingle/xmpp/xmppengine.h new file mode 100644 index 0000000000..2bc2453ce6 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppengine.h @@ -0,0 +1,332 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPENGINE_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPENGINE_H_ + +// also part of the API +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/jid.h" + + +namespace buzz { + +class XmppEngine; +class SaslHandler; +typedef void * XmppIqCookie; + +//! XMPP stanza error codes. +//! Used in XmppEngine.SendStanzaError(). +enum XmppStanzaError { + XSE_BAD_REQUEST, + XSE_CONFLICT, + XSE_FEATURE_NOT_IMPLEMENTED, + XSE_FORBIDDEN, + XSE_GONE, + XSE_INTERNAL_SERVER_ERROR, + XSE_ITEM_NOT_FOUND, + XSE_JID_MALFORMED, + XSE_NOT_ACCEPTABLE, + XSE_NOT_ALLOWED, + XSE_PAYMENT_REQUIRED, + XSE_RECIPIENT_UNAVAILABLE, + XSE_REDIRECT, + XSE_REGISTRATION_REQUIRED, + XSE_SERVER_NOT_FOUND, + XSE_SERVER_TIMEOUT, + XSE_RESOURCE_CONSTRAINT, + XSE_SERVICE_UNAVAILABLE, + XSE_SUBSCRIPTION_REQUIRED, + XSE_UNDEFINED_CONDITION, + XSE_UNEXPECTED_REQUEST, +}; + +// XmppReturnStatus +// This is used by API functions to synchronously return status. +enum XmppReturnStatus { + XMPP_RETURN_OK, + XMPP_RETURN_BADARGUMENT, + XMPP_RETURN_BADSTATE, + XMPP_RETURN_PENDING, + XMPP_RETURN_UNEXPECTED, + XMPP_RETURN_NOTYETIMPLEMENTED, +}; + +// TlsOptions +// This is used by API to identify TLS setting. +enum TlsOptions { + TLS_DISABLED, + TLS_ENABLED, + TLS_REQUIRED +}; + +//! Callback for socket output for an XmppEngine connection. +//! Register via XmppEngine.SetOutputHandler. An XmppEngine +//! can call back to this handler while it is processing +//! Connect, SendStanza, SendIq, Disconnect, or HandleInput. +class XmppOutputHandler { +public: + virtual ~XmppOutputHandler() {} + + //! Deliver the specified bytes to the XMPP socket. + virtual void WriteOutput(const char * bytes, size_t len) = 0; + + //! Initiate TLS encryption on the socket. + //! The implementation must verify that the SSL + //! certificate matches the given domainname. + virtual void StartTls(const std::string & domainname) = 0; + + //! Called when engine wants the connecton closed. + virtual void CloseConnection() = 0; +}; + +//! Callback to deliver engine state change notifications +//! to the object managing the engine. +class XmppSessionHandler { +public: + virtual ~XmppSessionHandler() {} + //! Called when engine changes state. Argument is new state. + virtual void OnStateChange(int state) = 0; +}; + +//! Callback to deliver stanzas to an Xmpp application module. +//! Register via XmppEngine.SetDefaultSessionHandler or via +//! XmppEngine.AddSessionHAndler. +class XmppStanzaHandler { +public: + virtual ~XmppStanzaHandler() {} + //! Process the given stanza. + //! The handler must return true if it has handled the stanza. + //! A false return value causes the stanza to be passed on to + //! the next registered handler. + virtual bool HandleStanza(const XmlElement * stanza) = 0; +}; + +//! Callback to deliver iq responses (results and errors). +//! Register while sending an iq via XmppEngine.SendIq. +//! Iq responses are routed to matching XmppIqHandlers in preference +//! to sending to any registered SessionHandlers. +class XmppIqHandler { +public: + virtual ~XmppIqHandler() {} + //! Called to handle the iq response. + //! The response may be either a result or an error, and will have + //! an 'id' that matches the request and a 'from' that matches the + //! 'to' of the request. Called no more than once; once this is + //! called, the handler is automatically unregistered. + virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) = 0; +}; + +//! The XMPP connection engine. +//! This engine implements the client side of the 'core' XMPP protocol. +//! To use it, register an XmppOutputHandler to handle socket output +//! and pass socket input to HandleInput. Then application code can +//! set up the connection with a user, password, and other settings, +//! and then call Connect() to initiate the connection. +//! An application can listen for events and receive stanzas by +//! registering an XmppStanzaHandler via AddStanzaHandler(). +class XmppEngine { +public: + static XmppEngine * Create(); + virtual ~XmppEngine() {} + + //! Error codes. See GetError(). + enum Error { + ERROR_NONE = 0, //!< No error + ERROR_XML, //!< Malformed XML or encoding error + ERROR_STREAM, //!< XMPP stream error - see GetStreamError() + ERROR_VERSION, //!< XMPP version error + ERROR_UNAUTHORIZED, //!< User is not authorized (rejected credentials) + ERROR_TLS, //!< TLS could not be negotiated + ERROR_AUTH, //!< Authentication could not be negotiated + ERROR_BIND, //!< Resource or session binding could not be negotiated + ERROR_CONNECTION_CLOSED,//!< Connection closed by output handler. + ERROR_DOCUMENT_CLOSED, //!< Closed by + ERROR_SOCKET, //!< Socket error + ERROR_NETWORK_TIMEOUT, //!< Some sort of timeout (eg., we never got the roster) + ERROR_MISSING_USERNAME //!< User has a Google Account but no nickname + }; + + //! States. See GetState(). + enum State { + STATE_NONE = 0, //!< Nonexistent state + STATE_START, //!< Initial state. + STATE_OPENING, //!< Exchanging stream headers, authenticating and so on. + STATE_OPEN, //!< Authenticated and bound. + STATE_CLOSED, //!< Session closed, possibly due to error. + }; + + // SOCKET INPUT AND OUTPUT ------------------------------------------------ + + //! Registers the handler for socket output + virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh) = 0; + + //! Provides socket input to the engine + virtual XmppReturnStatus HandleInput(const char * bytes, size_t len) = 0; + + //! Advises the engine that the socket has closed + virtual XmppReturnStatus ConnectionClosed(int subcode) = 0; + + // SESSION SETUP --------------------------------------------------------- + + //! Indicates the (bare) JID for the user to use. + virtual XmppReturnStatus SetUser(const Jid & jid)= 0; + + //! Get the login (bare) JID. + virtual const Jid & GetUser() = 0; + + //! Provides different methods for credentials for login. + //! Takes ownership of this object; deletes when login is done + virtual XmppReturnStatus SetSaslHandler(SaslHandler * h) = 0; + + //! Sets whether TLS will be used within the connection (default true). + virtual XmppReturnStatus SetTls(TlsOptions useTls) = 0; + + //! Sets an alternate domain from which we allows TLS certificates. + //! This is for use in the case where a we want to allow a proxy to + //! serve up its own certificate rather than one owned by the underlying + //! domain. + virtual XmppReturnStatus SetTlsServer(const std::string & proxy_hostname, + const std::string & proxy_domain) = 0; + + //! Gets whether TLS will be used within the connection. + virtual TlsOptions GetTls() = 0; + + //! Sets the request resource name, if any (optional). + //! Note that the resource name may be overridden by the server; after + //! binding, the actual resource name is available as part of FullJid(). + virtual XmppReturnStatus SetRequestedResource(const std::string& resource) = 0; + + //! Gets the request resource name. + virtual const std::string & GetRequestedResource() = 0; + + //! Sets language + virtual void SetLanguage(const std::string & lang) = 0; + + // SESSION MANAGEMENT --------------------------------------------------- + + //! Set callback for state changes. + virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler) = 0; + + //! Initiates the XMPP connection. + //! After supplying connection settings, call this once to initiate, + //! (optionally) encrypt, authenticate, and bind the connection. + virtual XmppReturnStatus Connect() = 0; + + //! The current engine state. + virtual State GetState() = 0; + + //! Returns true if the connection is encrypted (under TLS) + virtual bool IsEncrypted() = 0; + + //! The error code. + //! Consult this after XmppOutputHandler.OnClose(). + virtual Error GetError(int *subcode) = 0; + + //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM. + //! Notice the stanza returned is owned by the XmppEngine and + //! is deleted when the engine is destroyed. + virtual const XmlElement * GetStreamError() = 0; + + //! Closes down the connection. + //! Sends CloseConnection to output, and disconnects and registered + //! session handlers. After Disconnect completes, it is guaranteed + //! that no further callbacks will be made. + virtual XmppReturnStatus Disconnect() = 0; + + // APPLICATION USE ------------------------------------------------------- + + enum HandlerLevel { + HL_NONE = 0, + HL_PEEK, //!< Sees messages before all other processing; cannot abort + HL_SINGLE, //!< Watches for a single message, e.g., by id and sender + HL_SENDER, //!< Watches for a type of message from a specific sender + HL_TYPE, //!< Watches a type of message, e.g., all groupchat msgs + HL_ALL, //!< Watches all messages - gets last shot + HL_COUNT, //!< Count of handler levels + }; + + //! Adds a listener for session events. + //! Stanza delivery is chained to session handlers; the first to + //! return 'true' is the last to get each stanza. + virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler, HandlerLevel level = HL_PEEK) = 0; + + //! Removes a listener for session events. + virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler) = 0; + + //! Sends a stanza to the server. + virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza) = 0; + + //! Sends raw text to the server + virtual XmppReturnStatus SendRaw(const std::string & text) = 0; + + //! Sends an iq to the server, and registers a callback for the result. + //! Returns the cookie passed to the result handler. + virtual XmppReturnStatus SendIq(const XmlElement* pelStanza, + XmppIqHandler* iq_handler, + XmppIqCookie* cookie) = 0; + + //! Unregisters an iq callback handler given its cookie. + //! No callback will come to this handler after it's unregistered. + virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie, + XmppIqHandler** iq_handler) = 0; + + + //! Forms and sends an error in response to the given stanza. + //! Swaps to and from, sets type to "error", and adds error information + //! based on the passed code. Text is optional and may be STR_EMPTY. + virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text) = 0; + + //! The fullly bound JID. + //! This JID is only valid after binding has succeeded. If the value + //! is JID_NULL, the binding has not succeeded. + virtual const Jid & FullJid() = 0; + + //! The next unused iq id for this connection. + //! Call this when building iq stanzas, to ensure that each iq + //! gets its own unique id. + virtual std::string NextId() = 0; + +}; + +} + + +// Move these to a better location + +#define XMPP_FAILED(x) \ + ( (x) == buzz::XMPP_RETURN_OK ? false : true) \ + + +#define XMPP_SUCCEEDED(x) \ + ( (x) == buzz::XMPP_RETURN_OK ? true : false) \ + +#define IFR(x) \ + do { \ + xmpp_status = (x); \ + if (XMPP_FAILED(xmpp_status)) { \ + return xmpp_status; \ + } \ + } while (false) \ + + +#define IFC(x) \ + do { \ + xmpp_status = (x); \ + if (XMPP_FAILED(xmpp_status)) { \ + goto Cleanup; \ + } \ + } while (false) \ + + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPENGINE_H_ diff --git a/webrtc/libjingle/xmpp/xmppengine_unittest.cc b/webrtc/libjingle/xmpp/xmppengine_unittest.cc new file mode 100644 index 0000000000..d5afead660 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppengine_unittest.cc @@ -0,0 +1,327 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/plainsaslhandler.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::Jid; +using buzz::QName; +using buzz::XmlElement; +using buzz::XmppEngine; +using buzz::XmppIqCookie; +using buzz::XmppIqHandler; +using buzz::XmppTestHandler; +using buzz::QN_ID; +using buzz::QN_IQ; +using buzz::QN_TYPE; +using buzz::QN_ROSTER_QUERY; +using buzz::XMPP_RETURN_OK; +using buzz::XMPP_RETURN_BADARGUMENT; + +// XmppEngineTestIqHandler +// This class grabs the response to an IQ stanza and stores it in a string. +class XmppEngineTestIqHandler : public XmppIqHandler { + public: + virtual void IqResponse(XmppIqCookie, const XmlElement * stanza) { + ss_ << stanza->Str(); + } + + std::string IqResponseActivity() { + std::string result = ss_.str(); + ss_.str(""); + return result; + } + + private: + std::stringstream ss_; +}; + +class XmppEngineTest : public testing::Test { + public: + XmppEngine* engine() { return engine_.get(); } + XmppTestHandler* handler() { return handler_.get(); } + virtual void SetUp() { + engine_.reset(XmppEngine::Create()); + handler_.reset(new XmppTestHandler(engine_.get())); + + Jid jid("david@my-server"); + rtc::InsecureCryptStringImpl pass; + pass.password() = "david"; + engine_->SetSessionHandler(handler_.get()); + engine_->SetOutputHandler(handler_.get()); + engine_->AddStanzaHandler(handler_.get()); + engine_->SetUser(jid); + engine_->SetSaslHandler( + new buzz::PlainSaslHandler(jid, rtc::CryptString(pass), true)); + } + virtual void TearDown() { + handler_.reset(); + engine_.reset(); + } + void RunLogin(); + + private: + std::unique_ptr engine_; + std::unique_ptr handler_; +}; + +void XmppEngineTest::RunLogin() { + // Connect + EXPECT_EQ(XmppEngine::STATE_START, engine()->GetState()); + engine()->Connect(); + EXPECT_EQ(XmppEngine::STATE_OPENING, engine()->GetState()); + + EXPECT_EQ("[OPENING]", handler_->SessionActivity()); + + EXPECT_EQ("\r\n", handler_->OutputActivity()); + + std::string input = + ""; + engine()->HandleInput(input.c_str(), input.length()); + + input = + "" + "" + "" + "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", + handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("[START-TLS my-server]" + "\r\n", handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + input = + "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("AGRhdmlkAGRhdmlk", + handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("\r\n", handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + input = "" + "" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("" + "", + handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = "" + "" + "david@my-server/test"; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("" + "", + handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[OPEN]", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ(Jid("david@my-server/test"), engine()->FullJid()); +} + +// TestSuccessfulLogin() +// This function simply tests to see if a login works. This includes +// encryption and authentication +TEST_F(XmppEngineTest, TestSuccessfulLoginAndDisconnect) { + RunLogin(); + engine()->Disconnect(); + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppEngineTest, TestSuccessfulLoginAndConnectionClosed) { + RunLogin(); + engine()->ConnectionClosed(0); + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-CONNECTION-CLOSED]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + + +// TestNotXmpp() +// This tests the error case when connecting to a non XMPP service +TEST_F(XmppEngineTest, TestNotXmpp) { + // Connect + engine()->Connect(); + EXPECT_EQ("\r\n", handler()->OutputActivity()); + + // Send garbage response (courtesy of apache) + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[OPENING][CLOSED][ERROR-XML]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +// TestPassthrough() +// This tests that arbitrary stanzas can be passed to the server through +// the engine. +TEST_F(XmppEngineTest, TestPassthrough) { + // Queue up an app stanza + XmlElement application_stanza(QName("test", "app-stanza")); + application_stanza.AddText("this-is-a-test"); + engine()->SendStanza(&application_stanza); + + // Do the whole login handshake + RunLogin(); + + EXPECT_EQ("this-is-a-test" + "", handler()->OutputActivity()); + + // do another stanza + XmlElement roster_get(QN_IQ); + roster_get.AddAttr(QN_TYPE, "get"); + roster_get.AddAttr(QN_ID, engine()->NextId()); + roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); + engine()->SendStanza(&roster_get); + EXPECT_EQ("" + "", handler()->OutputActivity()); + + // now say the server ends the stream + engine()->HandleInput("", 16); + EXPECT_EQ("[CLOSED][ERROR-DOCUMENT-CLOSED]", handler()->SessionActivity()); + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +// TestIqCallback() +// This tests the routing of Iq stanzas and responses. +TEST_F(XmppEngineTest, TestIqCallback) { + XmppEngineTestIqHandler iq_response; + XmppIqCookie cookie; + + // Do the whole login handshake + RunLogin(); + + // Build an iq request + XmlElement roster_get(QN_IQ); + roster_get.AddAttr(QN_TYPE, "get"); + roster_get.AddAttr(QN_ID, engine()->NextId()); + roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); + engine()->SendIq(&roster_get, &iq_response, &cookie); + EXPECT_EQ("" + "", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); + EXPECT_EQ("", iq_response.IqResponseActivity()); + + // now say the server responds to the iq + std::string input = "" + "foo" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); + EXPECT_EQ("" + "foo" + "", iq_response.IqResponseActivity()); + + EXPECT_EQ(XMPP_RETURN_BADARGUMENT, engine()->RemoveIqHandler(cookie, NULL)); + + // Do it again with another id to test cancel + roster_get.SetAttr(QN_ID, engine()->NextId()); + engine()->SendIq(&roster_get, &iq_response, &cookie); + EXPECT_EQ("" + "", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); + EXPECT_EQ("", iq_response.IqResponseActivity()); + + // cancel the handler this time + EXPECT_EQ(XMPP_RETURN_OK, engine()->RemoveIqHandler(cookie, NULL)); + + // now say the server responds to the iq: the iq handler should not get it. + input = "bar" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("" + "bar" + "", handler()->StanzaActivity()); + EXPECT_EQ("", iq_response.IqResponseActivity()); + EXPECT_EQ("", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); +} diff --git a/webrtc/libjingle/xmpp/xmppengineimpl.cc b/webrtc/libjingle/xmpp/xmppengineimpl.cc new file mode 100644 index 0000000000..e77a1d9349 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppengineimpl.cc @@ -0,0 +1,446 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmppengineimpl.h" + +#include +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlprinter.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslhandler.h" +#include "webrtc/libjingle/xmpp/xmpplogintask.h" +#include "webrtc/base/common.h" + +namespace buzz { + +XmppEngine* XmppEngine::Create() { + return new XmppEngineImpl(); +} + + +XmppEngineImpl::XmppEngineImpl() + : stanza_parse_handler_(this), + stanza_parser_(&stanza_parse_handler_), + engine_entered_(0), + password_(), + requested_resource_(STR_EMPTY), + tls_option_(buzz::TLS_REQUIRED), + login_task_(new XmppLoginTask(this)), + next_id_(0), + state_(STATE_START), + encrypted_(false), + error_code_(ERROR_NONE), + subcode_(0), + stream_error_(), + raised_reset_(false), + output_handler_(NULL), + session_handler_(NULL), + iq_entries_(new IqEntryVector()), + sasl_handler_(), + output_(new std::stringstream()) { + for (int i = 0; i < HL_COUNT; i+= 1) { + stanza_handlers_[i].reset(new StanzaHandlerVector()); + } + + // Add XMPP namespaces to XML namespaces stack. + xmlns_stack_.AddXmlns("stream", "http://etherx.jabber.org/streams"); + xmlns_stack_.AddXmlns("", "jabber:client"); +} + +XmppEngineImpl::~XmppEngineImpl() { + DeleteIqCookies(); +} + +XmppReturnStatus XmppEngineImpl::SetOutputHandler( + XmppOutputHandler* output_handler) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + output_handler_ = output_handler; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::SetSessionHandler( + XmppSessionHandler* session_handler) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + session_handler_ = session_handler; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::HandleInput( + const char* bytes, size_t len) { + if (state_ < STATE_OPENING || state_ > STATE_OPEN) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + // TODO: The return value of the xml parser is not checked. + stanza_parser_.Parse(bytes, len, false); + + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::ConnectionClosed(int subcode) { + if (state_ != STATE_CLOSED) { + EnterExit ee(this); + // If told that connection closed and not already closed, + // then connection was unpexectedly dropped. + if (subcode) { + SignalError(ERROR_SOCKET, subcode); + } else { + SignalError(ERROR_CONNECTION_CLOSED, 0); // no subcode + } + } + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::SetTls(TlsOptions use_tls) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + tls_option_ = use_tls; + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::SetTlsServer( + const std::string& tls_server_hostname, + const std::string& tls_server_domain) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + tls_server_hostname_ = tls_server_hostname; + tls_server_domain_= tls_server_domain; + + return XMPP_RETURN_OK; +} + +TlsOptions XmppEngineImpl::GetTls() { + return tls_option_; +} + +XmppReturnStatus XmppEngineImpl::SetUser(const Jid& jid) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + user_jid_ = jid; + + return XMPP_RETURN_OK; +} + +const Jid& XmppEngineImpl::GetUser() { + return user_jid_; +} + +XmppReturnStatus XmppEngineImpl::SetSaslHandler(SaslHandler* sasl_handler) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + sasl_handler_.reset(sasl_handler); + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::SetRequestedResource( + const std::string& resource) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + requested_resource_ = resource; + + return XMPP_RETURN_OK; +} + +const std::string& XmppEngineImpl::GetRequestedResource() { + return requested_resource_; +} + +XmppReturnStatus XmppEngineImpl::AddStanzaHandler( + XmppStanzaHandler* stanza_handler, + XmppEngine::HandlerLevel level) { + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + + stanza_handlers_[level]->push_back(stanza_handler); + + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::RemoveStanzaHandler( + XmppStanzaHandler* stanza_handler) { + bool found = false; + + for (int level = 0; level < HL_COUNT; level += 1) { + StanzaHandlerVector::iterator new_end = + std::remove(stanza_handlers_[level]->begin(), + stanza_handlers_[level]->end(), + stanza_handler); + + if (new_end != stanza_handlers_[level]->end()) { + stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end()); + found = true; + } + } + + if (!found) + return XMPP_RETURN_BADARGUMENT; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::Connect() { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + // get the login task started + state_ = STATE_OPENING; + if (login_task_) { + login_task_->IncomingStanza(NULL, false); + if (login_task_->IsDone()) + login_task_.reset(); + } + + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::SendStanza(const XmlElement* element) { + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + if (login_task_) { + // still handshaking - then outbound stanzas are queued + login_task_->OutgoingStanza(element); + } else { + // handshake done - send straight through + InternalSendStanza(element); + } + + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::SendRaw(const std::string& text) { + if (state_ == STATE_CLOSED || login_task_) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + (*output_) << text; + + return XMPP_RETURN_OK; +} + +std::string XmppEngineImpl::NextId() { + std::stringstream ss; + ss << next_id_++; + return ss.str(); +} + +XmppReturnStatus XmppEngineImpl::Disconnect() { + if (state_ != STATE_CLOSED) { + EnterExit ee(this); + if (state_ == STATE_OPEN) + *output_ << ""; + state_ = STATE_CLOSED; + } + + return XMPP_RETURN_OK; +} + +void XmppEngineImpl::IncomingStart(const XmlElement* start) { + if (HasError() || raised_reset_) + return; + + if (login_task_) { + // start-stream should go to login task + login_task_->IncomingStanza(start, true); + if (login_task_->IsDone()) + login_task_.reset(); + } + else { + // if not logging in, it's an error to see a start + SignalError(ERROR_XML, 0); + } +} + +void XmppEngineImpl::IncomingStanza(const XmlElement* stanza) { + if (HasError() || raised_reset_) + return; + + if (stanza->Name() == QN_STREAM_ERROR) { + // Explicit XMPP stream error + SignalStreamError(stanza); + } else if (login_task_) { + // Handle login handshake + login_task_->IncomingStanza(stanza, false); + if (login_task_->IsDone()) + login_task_.reset(); + } else if (HandleIqResponse(stanza)) { + // iq is handled by above call + } else { + // give every "peek" handler a shot at all stanzas + for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) { + (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza); + } + + // give other handlers a shot in precedence order, stopping after handled + for (int level = HL_SINGLE; level <= HL_ALL; level += 1) { + for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) { + if ((*stanza_handlers_[level])[i]->HandleStanza(stanza)) + return; + } + } + + // If nobody wants to handle a stanza then send back an error. + // Only do this for IQ stanzas as messages should probably just be dropped + // and presence stanzas should certainly be dropped. + std::string type = stanza->Attr(QN_TYPE); + if (stanza->Name() == QN_IQ && + !(type == "error" || type == "result")) { + SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY); + } + } +} + +void XmppEngineImpl::IncomingEnd(bool isError) { + if (HasError() || raised_reset_) + return; + + SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0); +} + +void XmppEngineImpl::InternalSendStart(const std::string& to) { + std::string hostname = tls_server_hostname_; + if (hostname.empty()) + hostname = to; + + // If not language is specified, the spec says use * + std::string lang = lang_; + if (lang.length() == 0) + lang = "*"; + + // send stream-beginning + // note, we put a \r\n at tne end fo the first line to cause non-XMPP + // line-oriented servers (e.g., Apache) to reveal themselves more quickly. + *output_ << "\r\n"; +} + +void XmppEngineImpl::InternalSendStanza(const XmlElement* element) { + // It should really never be necessary to set a FROM attribute on a stanza. + // It is implied by the bind on the stream and if you get it wrong + // (by flipping from/to on a message?) the server will close the stream. + ASSERT(!element->HasAttr(QN_FROM)); + + XmlPrinter::PrintXml(output_.get(), element, &xmlns_stack_); +} + +std::string XmppEngineImpl::ChooseBestSaslMechanism( + const std::vector& mechanisms, bool encrypted) { + return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted); +} + +SaslMechanism* XmppEngineImpl::GetSaslMechanism(const std::string& name) { + return sasl_handler_->CreateSaslMechanism(name); +} + +void XmppEngineImpl::SignalBound(const Jid& fullJid) { + if (state_ == STATE_OPENING) { + bound_jid_ = fullJid; + state_ = STATE_OPEN; + } +} + +void XmppEngineImpl::SignalStreamError(const XmlElement* stream_error) { + if (state_ != STATE_CLOSED) { + stream_error_.reset(new XmlElement(*stream_error)); + SignalError(ERROR_STREAM, 0); + } +} + +void XmppEngineImpl::SignalError(Error error_code, int sub_code) { + if (state_ != STATE_CLOSED) { + error_code_ = error_code; + subcode_ = sub_code; + state_ = STATE_CLOSED; + } +} + +bool XmppEngineImpl::HasError() { + return error_code_ != ERROR_NONE; +} + +void XmppEngineImpl::StartTls(const std::string& domain) { + if (output_handler_) { + // As substitute for the real (login jid's) domain, we permit + // verifying a tls_server_domain_ instead, if one was passed. + // This allows us to avoid running a proxy that needs to handle + // valuable certificates. + output_handler_->StartTls( + tls_server_domain_.empty() ? domain : tls_server_domain_); + encrypted_ = true; + } +} + +XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine) + : engine_(engine), + state_(engine->state_) { + engine->engine_entered_ += 1; +} + +XmppEngineImpl::EnterExit::~EnterExit() { + XmppEngineImpl* engine = engine_; + + engine->engine_entered_ -= 1; + + bool closing = (engine->state_ != state_ && + engine->state_ == STATE_CLOSED); + bool flushing = closing || (engine->engine_entered_ == 0); + + if (engine->output_handler_ && flushing) { + std::string output = engine->output_->str(); + if (output.length() > 0) + engine->output_handler_->WriteOutput(output.c_str(), output.length()); + engine->output_->str(""); + + if (closing) { + engine->output_handler_->CloseConnection(); + engine->output_handler_ = 0; + } + } + + if (engine->engine_entered_) + return; + + if (engine->raised_reset_) { + engine->stanza_parser_.Reset(); + engine->raised_reset_ = false; + } + + if (engine->session_handler_) { + if (engine->state_ != state_) + engine->session_handler_->OnStateChange(engine->state_); + // Note: Handling of OnStateChange(CLOSED) should allow for the + // deletion of the engine, so no members should be accessed + // after this line. + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/xmppengineimpl.h b/webrtc/libjingle/xmpp/xmppengineimpl.h new file mode 100644 index 0000000000..e1f3061f90 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppengineimpl.h @@ -0,0 +1,267 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPENGINEIMPL_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPENGINEIMPL_H_ + +#include +#include +#include + +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmppstanzaparser.h" + +namespace buzz { + +class XmppLoginTask; +class XmppEngine; +class XmppIqEntry; +class SaslHandler; +class SaslMechanism; + +//! The XMPP connection engine. +//! This engine implements the client side of the 'core' XMPP protocol. +//! To use it, register an XmppOutputHandler to handle socket output +//! and pass socket input to HandleInput. Then application code can +//! set up the connection with a user, password, and other settings, +//! and then call Connect() to initiate the connection. +//! An application can listen for events and receive stanzas by +//! registering an XmppStanzaHandler via AddStanzaHandler(). +class XmppEngineImpl : public XmppEngine { + public: + XmppEngineImpl(); + virtual ~XmppEngineImpl(); + + // SOCKET INPUT AND OUTPUT ------------------------------------------------ + + //! Registers the handler for socket output + virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh); + + //! Provides socket input to the engine + virtual XmppReturnStatus HandleInput(const char* bytes, size_t len); + + //! Advises the engine that the socket has closed + virtual XmppReturnStatus ConnectionClosed(int subcode); + + // SESSION SETUP --------------------------------------------------------- + + //! Indicates the (bare) JID for the user to use. + virtual XmppReturnStatus SetUser(const Jid& jid); + + //! Get the login (bare) JID. + virtual const Jid& GetUser(); + + //! Indicates the autentication to use. Takes ownership of the object. + virtual XmppReturnStatus SetSaslHandler(SaslHandler* sasl_handler); + + //! Sets whether TLS will be used within the connection (default true). + virtual XmppReturnStatus SetTls(TlsOptions use_tls); + + //! Sets an alternate domain from which we allows TLS certificates. + //! This is for use in the case where a we want to allow a proxy to + //! serve up its own certificate rather than one owned by the underlying + //! domain. + virtual XmppReturnStatus SetTlsServer(const std::string& proxy_hostname, + const std::string& proxy_domain); + + //! Gets whether TLS will be used within the connection. + virtual TlsOptions GetTls(); + + //! Sets the request resource name, if any (optional). + //! Note that the resource name may be overridden by the server; after + //! binding, the actual resource name is available as part of FullJid(). + virtual XmppReturnStatus SetRequestedResource(const std::string& resource); + + //! Gets the request resource name. + virtual const std::string& GetRequestedResource(); + + //! Sets language + virtual void SetLanguage(const std::string& lang) { + lang_ = lang; + } + + // SESSION MANAGEMENT --------------------------------------------------- + + //! Set callback for state changes. + virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler); + + //! Initiates the XMPP connection. + //! After supplying connection settings, call this once to initiate, + //! (optionally) encrypt, authenticate, and bind the connection. + virtual XmppReturnStatus Connect(); + + //! The current engine state. + virtual State GetState() { return state_; } + + //! Returns true if the connection is encrypted (under TLS) + virtual bool IsEncrypted() { return encrypted_; } + + //! The error code. + //! Consult this after XmppOutputHandler.OnClose(). + virtual Error GetError(int *subcode) { + if (subcode) { + *subcode = subcode_; + } + return error_code_; + } + + //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM. + //! Notice the stanza returned is owned by the XmppEngine and + //! is deleted when the engine is destroyed. + virtual const XmlElement* GetStreamError() { return stream_error_.get(); } + + //! Closes down the connection. + //! Sends CloseConnection to output, and disconnects and registered + //! session handlers. After Disconnect completes, it is guaranteed + //! that no further callbacks will be made. + virtual XmppReturnStatus Disconnect(); + + // APPLICATION USE ------------------------------------------------------- + + //! Adds a listener for session events. + //! Stanza delivery is chained to session handlers; the first to + //! return 'true' is the last to get each stanza. + virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler, + XmppEngine::HandlerLevel level); + + //! Removes a listener for session events. + virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler); + + //! Sends a stanza to the server. + virtual XmppReturnStatus SendStanza(const XmlElement* stanza); + + //! Sends raw text to the server + virtual XmppReturnStatus SendRaw(const std::string& text); + + //! Sends an iq to the server, and registers a callback for the result. + //! Returns the cookie passed to the result handler. + virtual XmppReturnStatus SendIq(const XmlElement* stanza, + XmppIqHandler* iq_handler, + XmppIqCookie* cookie); + + //! Unregisters an iq callback handler given its cookie. + //! No callback will come to this handler after it's unregistered. + virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie, + XmppIqHandler** iq_handler); + + //! Forms and sends an error in response to the given stanza. + //! Swaps to and from, sets type to "error", and adds error information + //! based on the passed code. Text is optional and may be STR_EMPTY. + virtual XmppReturnStatus SendStanzaError(const XmlElement* pelOriginal, + XmppStanzaError code, + const std::string& text); + + //! The fullly bound JID. + //! This JID is only valid after binding has succeeded. If the value + //! is JID_NULL, the binding has not succeeded. + virtual const Jid& FullJid() { return bound_jid_; } + + //! The next unused iq id for this connection. + //! Call this when building iq stanzas, to ensure that each iq + //! gets its own unique id. + virtual std::string NextId(); + + private: + friend class XmppLoginTask; + friend class XmppIqEntry; + + void IncomingStanza(const XmlElement *stanza); + void IncomingStart(const XmlElement *stanza); + void IncomingEnd(bool isError); + + void InternalSendStart(const std::string& domainName); + void InternalSendStanza(const XmlElement* stanza); + std::string ChooseBestSaslMechanism( + const std::vector& mechanisms, bool encrypted); + SaslMechanism* GetSaslMechanism(const std::string& name); + void SignalBound(const Jid& fullJid); + void SignalStreamError(const XmlElement* streamError); + void SignalError(Error errorCode, int subCode); + bool HasError(); + void DeleteIqCookies(); + bool HandleIqResponse(const XmlElement* element); + void StartTls(const std::string& domain); + void RaiseReset() { raised_reset_ = true; } + + class StanzaParseHandler : public XmppStanzaParseHandler { + public: + StanzaParseHandler(XmppEngineImpl* outer) : outer_(outer) {} + virtual ~StanzaParseHandler() {} + + virtual void StartStream(const XmlElement* stream) { + outer_->IncomingStart(stream); + } + virtual void Stanza(const XmlElement* stanza) { + outer_->IncomingStanza(stanza); + } + virtual void EndStream() { + outer_->IncomingEnd(false); + } + virtual void XmlError() { + outer_->IncomingEnd(true); + } + + private: + XmppEngineImpl* const outer_; + }; + + class EnterExit { + public: + EnterExit(XmppEngineImpl* engine); + ~EnterExit(); + private: + XmppEngineImpl* engine_; + State state_; + }; + + friend class StanzaParseHandler; + friend class EnterExit; + + StanzaParseHandler stanza_parse_handler_; + XmppStanzaParser stanza_parser_; + + // state + int engine_entered_; + Jid user_jid_; + std::string password_; + std::string requested_resource_; + TlsOptions tls_option_; + std::string tls_server_hostname_; + std::string tls_server_domain_; + std::unique_ptr login_task_; + std::string lang_; + + int next_id_; + Jid bound_jid_; + State state_; + bool encrypted_; + Error error_code_; + int subcode_; + std::unique_ptr stream_error_; + bool raised_reset_; + XmppOutputHandler* output_handler_; + XmppSessionHandler* session_handler_; + + XmlnsStack xmlns_stack_; + + typedef std::vector StanzaHandlerVector; + std::unique_ptr stanza_handlers_[HL_COUNT]; + + typedef std::vector IqEntryVector; + std::unique_ptr iq_entries_; + + std::unique_ptr sasl_handler_; + + std::unique_ptr output_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPENGINEIMPL_H_ diff --git a/webrtc/libjingle/xmpp/xmppengineimpl_iq.cc b/webrtc/libjingle/xmpp/xmppengineimpl_iq.cc new file mode 100644 index 0000000000..23b9fc43ea --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppengineimpl_iq.cc @@ -0,0 +1,260 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppengineimpl.h" +#include "webrtc/base/common.h" + +namespace buzz { + +class XmppIqEntry { + XmppIqEntry(const std::string & id, const std::string & to, + XmppEngine * pxce, XmppIqHandler * iq_handler) : + id_(id), + to_(to), + engine_(pxce), + iq_handler_(iq_handler) { + } + +private: + friend class XmppEngineImpl; + + const std::string id_; + const std::string to_; + XmppEngine * const engine_; + XmppIqHandler * const iq_handler_; +}; + + +XmppReturnStatus +XmppEngineImpl::SendIq(const XmlElement * element, XmppIqHandler * iq_handler, + XmppIqCookie* cookie) { + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + if (NULL == iq_handler) + return XMPP_RETURN_BADARGUMENT; + if (!element || element->Name() != QN_IQ) + return XMPP_RETURN_BADARGUMENT; + + const std::string& type = element->Attr(QN_TYPE); + if (type != "get" && type != "set") + return XMPP_RETURN_BADARGUMENT; + + if (!element->HasAttr(QN_ID)) + return XMPP_RETURN_BADARGUMENT; + const std::string& id = element->Attr(QN_ID); + + XmppIqEntry * iq_entry = new XmppIqEntry(id, + element->Attr(QN_TO), + this, iq_handler); + iq_entries_->push_back(iq_entry); + SendStanza(element); + + if (cookie) + *cookie = iq_entry; + + return XMPP_RETURN_OK; +} + + +XmppReturnStatus +XmppEngineImpl::RemoveIqHandler(XmppIqCookie cookie, + XmppIqHandler ** iq_handler) { + + std::vector >::iterator pos; + + pos = std::find(iq_entries_->begin(), + iq_entries_->end(), + reinterpret_cast(cookie)); + + if (pos == iq_entries_->end()) + return XMPP_RETURN_BADARGUMENT; + + XmppIqEntry* entry = *pos; + iq_entries_->erase(pos); + if (iq_handler) + *iq_handler = entry->iq_handler_; + delete entry; + + return XMPP_RETURN_OK; +} + +void +XmppEngineImpl::DeleteIqCookies() { + for (size_t i = 0; i < iq_entries_->size(); i += 1) { + XmppIqEntry * iq_entry_ = (*iq_entries_)[i]; + (*iq_entries_)[i] = NULL; + delete iq_entry_; + } + iq_entries_->clear(); +} + +static void +AecImpl(XmlElement * error_element, const QName & name, + const char * type, const char * code) { + error_element->AddElement(new XmlElement(QN_ERROR)); + error_element->AddAttr(QN_CODE, code, 1); + error_element->AddAttr(QN_TYPE, type, 1); + error_element->AddElement(new XmlElement(name, true), 1); +} + + +static void +AddErrorCode(XmlElement * error_element, XmppStanzaError code) { + switch (code) { + case XSE_BAD_REQUEST: + AecImpl(error_element, QN_STANZA_BAD_REQUEST, "modify", "400"); + break; + case XSE_CONFLICT: + AecImpl(error_element, QN_STANZA_CONFLICT, "cancel", "409"); + break; + case XSE_FEATURE_NOT_IMPLEMENTED: + AecImpl(error_element, QN_STANZA_FEATURE_NOT_IMPLEMENTED, + "cancel", "501"); + break; + case XSE_FORBIDDEN: + AecImpl(error_element, QN_STANZA_FORBIDDEN, "auth", "403"); + break; + case XSE_GONE: + AecImpl(error_element, QN_STANZA_GONE, "modify", "302"); + break; + case XSE_INTERNAL_SERVER_ERROR: + AecImpl(error_element, QN_STANZA_INTERNAL_SERVER_ERROR, "wait", "500"); + break; + case XSE_ITEM_NOT_FOUND: + AecImpl(error_element, QN_STANZA_ITEM_NOT_FOUND, "cancel", "404"); + break; + case XSE_JID_MALFORMED: + AecImpl(error_element, QN_STANZA_JID_MALFORMED, "modify", "400"); + break; + case XSE_NOT_ACCEPTABLE: + AecImpl(error_element, QN_STANZA_NOT_ACCEPTABLE, "cancel", "406"); + break; + case XSE_NOT_ALLOWED: + AecImpl(error_element, QN_STANZA_NOT_ALLOWED, "cancel", "405"); + break; + case XSE_PAYMENT_REQUIRED: + AecImpl(error_element, QN_STANZA_PAYMENT_REQUIRED, "auth", "402"); + break; + case XSE_RECIPIENT_UNAVAILABLE: + AecImpl(error_element, QN_STANZA_RECIPIENT_UNAVAILABLE, "wait", "404"); + break; + case XSE_REDIRECT: + AecImpl(error_element, QN_STANZA_REDIRECT, "modify", "302"); + break; + case XSE_REGISTRATION_REQUIRED: + AecImpl(error_element, QN_STANZA_REGISTRATION_REQUIRED, "auth", "407"); + break; + case XSE_SERVER_NOT_FOUND: + AecImpl(error_element, QN_STANZA_REMOTE_SERVER_NOT_FOUND, + "cancel", "404"); + break; + case XSE_SERVER_TIMEOUT: + AecImpl(error_element, QN_STANZA_REMOTE_SERVER_TIMEOUT, "wait", "502"); + break; + case XSE_RESOURCE_CONSTRAINT: + AecImpl(error_element, QN_STANZA_RESOURCE_CONSTRAINT, "wait", "500"); + break; + case XSE_SERVICE_UNAVAILABLE: + AecImpl(error_element, QN_STANZA_SERVICE_UNAVAILABLE, "cancel", "503"); + break; + case XSE_SUBSCRIPTION_REQUIRED: + AecImpl(error_element, QN_STANZA_SUBSCRIPTION_REQUIRED, "auth", "407"); + break; + case XSE_UNDEFINED_CONDITION: + AecImpl(error_element, QN_STANZA_UNDEFINED_CONDITION, "wait", "500"); + break; + case XSE_UNEXPECTED_REQUEST: + AecImpl(error_element, QN_STANZA_UNEXPECTED_REQUEST, "wait", "400"); + break; + } +} + + +XmppReturnStatus +XmppEngineImpl::SendStanzaError(const XmlElement * element_original, + XmppStanzaError code, + const std::string & text) { + + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + + XmlElement error_element(element_original->Name()); + error_element.AddAttr(QN_TYPE, "error"); + + // copy attrs, copy 'from' to 'to' and strip 'from' + for (const XmlAttr * attribute = element_original->FirstAttr(); + attribute; attribute = attribute->NextAttr()) { + QName name = attribute->Name(); + if (name == QN_TO) + continue; // no need to put a from attr. Server will stamp stanza + else if (name == QN_FROM) + name = QN_TO; + else if (name == QN_TYPE) + continue; + error_element.AddAttr(name, attribute->Value()); + } + + // copy children + for (const XmlChild * child = element_original->FirstChild(); + child; + child = child->NextChild()) { + if (child->IsText()) { + error_element.AddText(child->AsText()->Text()); + } else { + error_element.AddElement(new XmlElement(*(child->AsElement()))); + } + } + + // add error information + AddErrorCode(&error_element, code); + if (text != STR_EMPTY) { + XmlElement * text_element = new XmlElement(QN_STANZA_TEXT, true); + text_element->AddText(text); + error_element.AddElement(text_element); + } + + SendStanza(&error_element); + + return XMPP_RETURN_OK; +} + + +bool +XmppEngineImpl::HandleIqResponse(const XmlElement * element) { + if (iq_entries_->empty()) + return false; + if (element->Name() != QN_IQ) + return false; + std::string type = element->Attr(QN_TYPE); + if (type != "result" && type != "error") + return false; + if (!element->HasAttr(QN_ID)) + return false; + std::string id = element->Attr(QN_ID); + std::string from = element->Attr(QN_FROM); + + for (std::vector::iterator it = iq_entries_->begin(); + it != iq_entries_->end(); it += 1) { + XmppIqEntry * iq_entry = *it; + if (iq_entry->id_ == id && iq_entry->to_ == from) { + iq_entries_->erase(it); + iq_entry->iq_handler_->IqResponse(iq_entry, element); + delete iq_entry; + return true; + } + } + + return false; +} + +} diff --git a/webrtc/libjingle/xmpp/xmpplogintask.cc b/webrtc/libjingle/xmpp/xmpplogintask.cc new file mode 100644 index 0000000000..e39713d463 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpplogintask.cc @@ -0,0 +1,380 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmpplogintask.h" + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/saslmechanism.h" +#include "webrtc/libjingle/xmpp/xmppengineimpl.h" +#include "webrtc/base/base64.h" +#include "webrtc/base/common.h" + +using rtc::ConstantLabel; + +namespace buzz { + +#if !defined(NDEBUG) +const ConstantLabel XmppLoginTask::LOGINTASK_STATES[] = { + KLABEL(LOGINSTATE_INIT), + KLABEL(LOGINSTATE_STREAMSTART_SENT), + KLABEL(LOGINSTATE_STARTED_XMPP), + KLABEL(LOGINSTATE_TLS_INIT), + KLABEL(LOGINSTATE_AUTH_INIT), + KLABEL(LOGINSTATE_BIND_INIT), + KLABEL(LOGINSTATE_TLS_REQUESTED), + KLABEL(LOGINSTATE_SASL_RUNNING), + KLABEL(LOGINSTATE_BIND_REQUESTED), + KLABEL(LOGINSTATE_SESSION_REQUESTED), + KLABEL(LOGINSTATE_DONE), + LASTLABEL +}; +#endif +XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) : + pctx_(pctx), + authNeeded_(true), + allowNonGoogleLogin_(true), + state_(LOGINSTATE_INIT), + pelStanza_(NULL), + isStart_(false), + iqId_(STR_EMPTY), + pelFeatures_(), + fullJid_(STR_EMPTY), + streamId_(STR_EMPTY), + pvecQueuedStanzas_(new std::vector()), + sasl_mech_() { +} + +XmppLoginTask::~XmppLoginTask() { + for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) + delete (*pvecQueuedStanzas_)[i]; +} + +void +XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) { + pelStanza_ = element; + isStart_ = isStart; + Advance(); + pelStanza_ = NULL; + isStart_ = false; +} + +const XmlElement * +XmppLoginTask::NextStanza() { + const XmlElement * result = pelStanza_; + pelStanza_ = NULL; + return result; +} + +bool +XmppLoginTask::Advance() { + + for (;;) { + + const XmlElement * element = NULL; + +#if !defined(NDEBUG) + LOG(LS_VERBOSE) << "XmppLoginTask::Advance - " + << rtc::ErrorName(state_, LOGINTASK_STATES); +#endif + + switch (state_) { + + case LOGINSTATE_INIT: { + pctx_->RaiseReset(); + pelFeatures_.reset(NULL); + + // The proper domain to verify against is the real underlying + // domain - i.e., the domain that owns the JID. Our XmppEngineImpl + // also allows matching against a proxy domain instead, if it is told + // to do so - see the implementation of XmppEngineImpl::StartTls and + // XmppEngine::SetTlsServerDomain to see how you can use that feature + pctx_->InternalSendStart(pctx_->user_jid_.domain()); + state_ = LOGINSTATE_STREAMSTART_SENT; + break; + } + + case LOGINSTATE_STREAMSTART_SENT: { + if (NULL == (element = NextStanza())) + return true; + + if (!isStart_ || !HandleStartStream(element)) + return Failure(XmppEngine::ERROR_VERSION); + + state_ = LOGINSTATE_STARTED_XMPP; + return true; + } + + case LOGINSTATE_STARTED_XMPP: { + if (NULL == (element = NextStanza())) + return true; + + if (!HandleFeatures(element)) + return Failure(XmppEngine::ERROR_VERSION); + + bool tls_present = (GetFeature(QN_TLS_STARTTLS) != NULL); + // Error if TLS required but not present. + if (pctx_->tls_option_ == buzz::TLS_REQUIRED && !tls_present) { + return Failure(XmppEngine::ERROR_TLS); + } + // Use TLS if required or enabled, and also available + if ((pctx_->tls_option_ == buzz::TLS_REQUIRED || + pctx_->tls_option_ == buzz::TLS_ENABLED) && tls_present) { + state_ = LOGINSTATE_TLS_INIT; + continue; + } + + if (authNeeded_) { + state_ = LOGINSTATE_AUTH_INIT; + continue; + } + + state_ = LOGINSTATE_BIND_INIT; + continue; + } + + case LOGINSTATE_TLS_INIT: { + const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS); + if (!pelTls) + return Failure(XmppEngine::ERROR_TLS); + + XmlElement el(QN_TLS_STARTTLS, true); + pctx_->InternalSendStanza(&el); + state_ = LOGINSTATE_TLS_REQUESTED; + continue; + } + + case LOGINSTATE_TLS_REQUESTED: { + if (NULL == (element = NextStanza())) + return true; + if (element->Name() != QN_TLS_PROCEED) + return Failure(XmppEngine::ERROR_TLS); + + // The proper domain to verify against is the real underlying + // domain - i.e., the domain that owns the JID. Our XmppEngineImpl + // also allows matching against a proxy domain instead, if it is told + // to do so - see the implementation of XmppEngineImpl::StartTls and + // XmppEngine::SetTlsServerDomain to see how you can use that feature + pctx_->StartTls(pctx_->user_jid_.domain()); + pctx_->tls_option_ = buzz::TLS_ENABLED; + state_ = LOGINSTATE_INIT; + continue; + } + + case LOGINSTATE_AUTH_INIT: { + const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS); + if (!pelSaslAuth) { + return Failure(XmppEngine::ERROR_AUTH); + } + + // Collect together the SASL auth mechanisms presented by the server + std::vector mechanisms; + for (const XmlElement * pelMech = + pelSaslAuth->FirstNamed(QN_SASL_MECHANISM); + pelMech; + pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) { + + mechanisms.push_back(pelMech->BodyText()); + } + + // Given all the mechanisms, choose the best + std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted())); + if (choice.empty()) { + return Failure(XmppEngine::ERROR_AUTH); + } + + // No recognized auth mechanism - that's an error + sasl_mech_.reset(pctx_->GetSaslMechanism(choice)); + if (!sasl_mech_) { + return Failure(XmppEngine::ERROR_AUTH); + } + + // OK, let's start it. + XmlElement * auth = sasl_mech_->StartSaslAuth(); + if (auth == NULL) { + return Failure(XmppEngine::ERROR_AUTH); + } + if (allowNonGoogleLogin_) { + // Setting the following two attributes is required to support + // non-google ids. + + // Allow login with non-google id accounts. + auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true"); + + // Allow login with either the non-google id or the friendly email. + auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true"); + } + + pctx_->InternalSendStanza(auth); + delete auth; + state_ = LOGINSTATE_SASL_RUNNING; + continue; + } + + case LOGINSTATE_SASL_RUNNING: { + if (NULL == (element = NextStanza())) + return true; + if (element->Name().Namespace() != NS_SASL) + return Failure(XmppEngine::ERROR_AUTH); + if (element->Name() == QN_SASL_CHALLENGE) { + XmlElement * response = sasl_mech_->HandleSaslChallenge(element); + if (response == NULL) { + return Failure(XmppEngine::ERROR_AUTH); + } + pctx_->InternalSendStanza(response); + delete response; + state_ = LOGINSTATE_SASL_RUNNING; + continue; + } + if (element->Name() != QN_SASL_SUCCESS) { + return Failure(XmppEngine::ERROR_UNAUTHORIZED); + } + + // Authenticated! + authNeeded_ = false; + state_ = LOGINSTATE_INIT; + continue; + } + + case LOGINSTATE_BIND_INIT: { + const XmlElement * pelBindFeature = GetFeature(QN_BIND_BIND); + const XmlElement * pelSessionFeature = GetFeature(QN_SESSION_SESSION); + if (!pelBindFeature || !pelSessionFeature) + return Failure(XmppEngine::ERROR_BIND); + + XmlElement iq(QN_IQ); + iq.AddAttr(QN_TYPE, "set"); + + iqId_ = pctx_->NextId(); + iq.AddAttr(QN_ID, iqId_); + iq.AddElement(new XmlElement(QN_BIND_BIND, true)); + + if (pctx_->requested_resource_ != STR_EMPTY) { + iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1); + iq.AddText(pctx_->requested_resource_, 2); + } + pctx_->InternalSendStanza(&iq); + state_ = LOGINSTATE_BIND_REQUESTED; + continue; + } + + case LOGINSTATE_BIND_REQUESTED: { + if (NULL == (element = NextStanza())) + return true; + + if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ || + element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set") + return true; + + if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL || + element->FirstElement()->Name() != QN_BIND_BIND) + return Failure(XmppEngine::ERROR_BIND); + + fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID)); + if (!fullJid_.IsFull()) { + return Failure(XmppEngine::ERROR_BIND); + } + + // now request session + XmlElement iq(QN_IQ); + iq.AddAttr(QN_TYPE, "set"); + + iqId_ = pctx_->NextId(); + iq.AddAttr(QN_ID, iqId_); + iq.AddElement(new XmlElement(QN_SESSION_SESSION, true)); + pctx_->InternalSendStanza(&iq); + + state_ = LOGINSTATE_SESSION_REQUESTED; + continue; + } + + case LOGINSTATE_SESSION_REQUESTED: { + if (NULL == (element = NextStanza())) + return true; + if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ || + element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set") + return false; + + if (element->Attr(QN_TYPE) != "result") + return Failure(XmppEngine::ERROR_BIND); + + pctx_->SignalBound(fullJid_); + FlushQueuedStanzas(); + state_ = LOGINSTATE_DONE; + return true; + } + + case LOGINSTATE_DONE: + return false; + } + } +} + +bool +XmppLoginTask::HandleStartStream(const XmlElement *element) { + + if (element->Name() != QN_STREAM_STREAM) + return false; + + if (element->Attr(QN_XMLNS) != "jabber:client") + return false; + + if (element->Attr(QN_VERSION) != "1.0") + return false; + + if (!element->HasAttr(QN_ID)) + return false; + + streamId_ = element->Attr(QN_ID); + + return true; +} + +bool +XmppLoginTask::HandleFeatures(const XmlElement *element) { + if (element->Name() != QN_STREAM_FEATURES) + return false; + + pelFeatures_.reset(new XmlElement(*element)); + return true; +} + +const XmlElement * +XmppLoginTask::GetFeature(const QName & name) { + return pelFeatures_->FirstNamed(name); +} + +bool +XmppLoginTask::Failure(XmppEngine::Error reason) { + state_ = LOGINSTATE_DONE; + pctx_->SignalError(reason, 0); + return false; +} + +void +XmppLoginTask::OutgoingStanza(const XmlElement * element) { + XmlElement * pelCopy = new XmlElement(*element); + pvecQueuedStanzas_->push_back(pelCopy); +} + +void +XmppLoginTask::FlushQueuedStanzas() { + for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) { + pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]); + delete (*pvecQueuedStanzas_)[i]; + } + pvecQueuedStanzas_->clear(); +} + +} diff --git a/webrtc/libjingle/xmpp/xmpplogintask.h b/webrtc/libjingle/xmpp/xmpplogintask.h new file mode 100644 index 0000000000..c015c2e7a1 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpplogintask.h @@ -0,0 +1,87 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_LOGINTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_LOGINTASK_H_ + +#include +#include +#include + +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/logging.h" + +namespace buzz { + +class XmlElement; +class XmppEngineImpl; +class SaslMechanism; + + +// TODO: Rename to LoginTask. +class XmppLoginTask { + +public: + XmppLoginTask(XmppEngineImpl *pctx); + ~XmppLoginTask(); + + bool IsDone() + { return state_ == LOGINSTATE_DONE; } + void IncomingStanza(const XmlElement * element, bool isStart); + void OutgoingStanza(const XmlElement *element); + void set_allow_non_google_login(bool b) + { allowNonGoogleLogin_ = b; } + +private: + enum LoginTaskState { + LOGINSTATE_INIT = 0, + LOGINSTATE_STREAMSTART_SENT, + LOGINSTATE_STARTED_XMPP, + LOGINSTATE_TLS_INIT, + LOGINSTATE_AUTH_INIT, + LOGINSTATE_BIND_INIT, + LOGINSTATE_TLS_REQUESTED, + LOGINSTATE_SASL_RUNNING, + LOGINSTATE_BIND_REQUESTED, + LOGINSTATE_SESSION_REQUESTED, + LOGINSTATE_DONE, + }; + + const XmlElement * NextStanza(); + bool Advance(); + bool HandleStartStream(const XmlElement * element); + bool HandleFeatures(const XmlElement * element); + const XmlElement * GetFeature(const QName & name); + bool Failure(XmppEngine::Error reason); + void FlushQueuedStanzas(); + + XmppEngineImpl * pctx_; + bool authNeeded_; + bool allowNonGoogleLogin_; + LoginTaskState state_; + const XmlElement * pelStanza_; + bool isStart_; + std::string iqId_; + std::unique_ptr pelFeatures_; + Jid fullJid_; + std::string streamId_; + std::unique_ptr > pvecQueuedStanzas_; + + std::unique_ptr sasl_mech_; + +#if !defined(NDEBUG) + static const rtc::ConstantLabel LOGINTASK_STATES[]; +#endif +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_LOGINTASK_H_ diff --git a/webrtc/libjingle/xmpp/xmpplogintask_unittest.cc b/webrtc/libjingle/xmpp/xmpplogintask_unittest.cc new file mode 100644 index 0000000000..18fce97645 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpplogintask_unittest.cc @@ -0,0 +1,637 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/plainsaslhandler.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/common.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/gunit.h" +#include "webrtc/typedefs.h" + +using buzz::Jid; +using buzz::QName; +using buzz::XmlElement; +using buzz::XmppEngine; +using buzz::XmppTestHandler; + +enum XlttStage { + XLTT_STAGE_CONNECT = 0, + XLTT_STAGE_STREAMSTART, + XLTT_STAGE_TLS_FEATURES, + XLTT_STAGE_TLS_PROCEED, + XLTT_STAGE_ENCRYPTED_START, + XLTT_STAGE_AUTH_FEATURES, + XLTT_STAGE_AUTH_SUCCESS, + XLTT_STAGE_AUTHENTICATED_START, + XLTT_STAGE_BIND_FEATURES, + XLTT_STAGE_BIND_SUCCESS, + XLTT_STAGE_SESSION_SUCCESS, +}; + +class XmppLoginTaskTest : public testing::Test { + public: + XmppEngine* engine() { return engine_.get(); } + XmppTestHandler* handler() { return handler_.get(); } + virtual void SetUp() { + engine_.reset(XmppEngine::Create()); + handler_.reset(new XmppTestHandler(engine_.get())); + + Jid jid("david@my-server"); + rtc::InsecureCryptStringImpl pass; + pass.password() = "david"; + engine_->SetSessionHandler(handler_.get()); + engine_->SetOutputHandler(handler_.get()); + engine_->AddStanzaHandler(handler_.get()); + engine_->SetUser(jid); + engine_->SetSaslHandler( + new buzz::PlainSaslHandler(jid, rtc::CryptString(pass), true)); + } + virtual void TearDown() { + handler_.reset(); + engine_.reset(); + } + void RunPartialLogin(XlttStage startstage, XlttStage endstage); + void SetTlsOptions(buzz::TlsOptions option); + + private: + std::unique_ptr engine_; + std::unique_ptr handler_; +}; + +void XmppLoginTaskTest::SetTlsOptions(buzz::TlsOptions option) { + engine_->SetTls(option); +} +void XmppLoginTaskTest::RunPartialLogin(XlttStage startstage, + XlttStage endstage) { + std::string input; + + switch (startstage) { + case XLTT_STAGE_CONNECT: { + engine_->Connect(); + XmlElement appStanza(QName("test", "app-stanza")); + appStanza.AddText("this-is-a-test"); + engine_->SendStanza(&appStanza); + + EXPECT_EQ("\r\n", handler_->OutputActivity()); + EXPECT_EQ("[OPENING]", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + if (endstage == XLTT_STAGE_CONNECT) + return; + FALLTHROUGH(); + } + + case XLTT_STAGE_STREAMSTART: { + input = ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->OutputActivity()); + if (endstage == XLTT_STAGE_STREAMSTART) + return; + FALLTHROUGH(); + } + + case XLTT_STAGE_TLS_FEATURES: { + input = "" + "" + ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", + handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_TLS_FEATURES) + return; + FALLTHROUGH(); + } + + case XLTT_STAGE_TLS_PROCEED: { + input = std::string(""); + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("[START-TLS my-server]" + "\r\n", handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_TLS_PROCEED) + return; + FALLTHROUGH(); + } + + case XLTT_STAGE_ENCRYPTED_START: { + input = std::string(""); + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->OutputActivity()); + if (endstage == XLTT_STAGE_ENCRYPTED_START) + return; + FALLTHROUGH(); + } + + case XLTT_STAGE_AUTH_FEATURES: { + input = "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("AGRhdmlkAGRhdmlk", + handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_AUTH_FEATURES) + return; + FALLTHROUGH(); + } + + case XLTT_STAGE_AUTH_SUCCESS: { + input = ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("\r\n", handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_AUTH_SUCCESS) + return; + FALLTHROUGH(); + } + + case XLTT_STAGE_AUTHENTICATED_START: { + input = std::string(""); + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->OutputActivity()); + if (endstage == XLTT_STAGE_AUTHENTICATED_START) + return; + FALLTHROUGH(); + } + + case XLTT_STAGE_BIND_FEATURES: { + input = "" + "" + "" + ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("" + "", + handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_BIND_FEATURES) + return; + FALLTHROUGH(); + } + + case XLTT_STAGE_BIND_SUCCESS: { + input = "" + "" + "david@my-server/test"; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("" + "", + handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_BIND_SUCCESS) + return; + FALLTHROUGH(); + } + + case XLTT_STAGE_SESSION_SUCCESS: { + input = ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("this-is-a-test" + "", handler_->OutputActivity()); + EXPECT_EQ("[OPEN]", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + if (endstage == XLTT_STAGE_SESSION_SUCCESS) + return; + FALLTHROUGH(); + } + default: + break; + } +} + +TEST_F(XmppLoginTaskTest, TestUtf8Good) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_CONNECT); + + std::string input = "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestNonUtf8Bad) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_CONNECT); + + std::string input = "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-XML]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestNoFeatures) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsRequiredNotPresent) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-TLS]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsRequeiredAndPresent) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "" + "" + "" + "X-GOOGLE-TOKEN" + "PLAIN" + "X-OAUTH2" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("", + handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsEnabledNotPresent) { + SetTlsOptions(buzz::TLS_ENABLED); + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("AGRhdmlkAGRhdmlk", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsEnabledAndPresent) { + SetTlsOptions(buzz::TLS_ENABLED); + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "X-GOOGLE-TOKEN" + "PLAIN" + "X-OAUTH2" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("AGRhdmlkAGRhdmlk", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsDisabledNotPresent) { + SetTlsOptions(buzz::TLS_DISABLED); + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("AGRhdmlkAGRhdmlk", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsDisabledAndPresent) { + SetTlsOptions(buzz::TLS_DISABLED); + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "X-GOOGLE-TOKEN" + "PLAIN" + "X-OAUTH2" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("AGRhdmlkAGRhdmlk", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsFailure) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_TLS_FEATURES); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-TLS]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsBadStream) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_TLS_PROCEED); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestMissingSaslPlain) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_ENCRYPTED_START); + + std::string input = "" + "" + "DIGEST-MD5" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-AUTH]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestWrongPassword) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTH_FEATURES); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-UNAUTHORIZED]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestAuthBadStream) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTH_SUCCESS); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestMissingBindFeature) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTHENTICATED_START); + + std::string input = "" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); +} + +TEST_F(XmppLoginTaskTest, TestMissingSessionFeature) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTHENTICATED_START); + + std::string input = "" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +/* TODO: Handle this case properly inside XmppLoginTask. +TEST_F(XmppLoginTaskTest, TestBindFailure1) { + // check wrong JID + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES); + + std::string input = "" + "" + "davey@my-server/test"; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} +*/ + +TEST_F(XmppLoginTaskTest, TestBindFailure2) { + // check missing JID + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES); + + std::string input = "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestBindFailure3) { + // check plain failure + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestBindFailure4) { + // check wrong id to ignore + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + // continue after an ignored iq + RunPartialLogin(XLTT_STAGE_BIND_SUCCESS, XLTT_STAGE_SESSION_SUCCESS); +} + +TEST_F(XmppLoginTaskTest, TestSessionFailurePlain1) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_SUCCESS); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); +} + +TEST_F(XmppLoginTaskTest, TestSessionFailurePlain2) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_SUCCESS); + + // check reverse iq to ignore + // TODO: consider queueing or passing through? + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + + // continue after an ignored iq + RunPartialLogin(XLTT_STAGE_SESSION_SUCCESS, XLTT_STAGE_SESSION_SUCCESS); +} + +TEST_F(XmppLoginTaskTest, TestBadXml) { + int errorKind = 0; + for (XlttStage stage = XLTT_STAGE_CONNECT; + stage <= XLTT_STAGE_SESSION_SUCCESS; + stage = static_cast(stage + 1)) { + RunPartialLogin(XLTT_STAGE_CONNECT, stage); + + std::string input; + switch (errorKind++ % 5) { + case 0: input = "&syntax;"; break; + case 1: input = ""; break; + case 2: input = ""; break; + case 3: input = "<>"; break; + case 4: input = ""; break; + } + + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-XML]", handler()->SessionActivity()); + + TearDown(); + SetUp(); + } +} + +TEST_F(XmppLoginTaskTest, TestStreamError) { + for (XlttStage stage = XLTT_STAGE_CONNECT; + stage <= XLTT_STAGE_SESSION_SUCCESS; + stage = static_cast(stage + 1)) { + switch (stage) { + case XLTT_STAGE_CONNECT: + case XLTT_STAGE_TLS_PROCEED: + case XLTT_STAGE_AUTH_SUCCESS: + continue; + default: + break; + } + + RunPartialLogin(XLTT_STAGE_CONNECT, stage); + + std::string input = "" + "" + "" + "Some special application diagnostic information!" + "" + "" + ""; + + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-STREAM]", handler()->SessionActivity()); + + EXPECT_EQ("" + "" + "" + "Some special application diagnostic information!" + "" + "" + "", engine()->GetStreamError()->Str()); + + TearDown(); + SetUp(); + } +} + diff --git a/webrtc/libjingle/xmpp/xmpppump.cc b/webrtc/libjingle/xmpp/xmpppump.cc new file mode 100644 index 0000000000..abd740639a --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpppump.cc @@ -0,0 +1,67 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmpppump.h" + +#include "webrtc/libjingle/xmpp/xmppauth.h" + +namespace buzz { + +XmppPump::XmppPump(XmppPumpNotify * notify) { + state_ = buzz::XmppEngine::STATE_NONE; + notify_ = notify; + client_ = new buzz::XmppClient(this); // NOTE: deleted by TaskRunner +} + +void XmppPump::DoLogin(const buzz::XmppClientSettings & xcs, + buzz::AsyncSocket* socket, + buzz::PreXmppAuth* auth) { + OnStateChange(buzz::XmppEngine::STATE_START); + if (!AllChildrenDone()) { + client_->SignalStateChange.connect(this, &XmppPump::OnStateChange); + client_->Connect(xcs, "", socket, auth); + client_->Start(); + } +} + +void XmppPump::DoDisconnect() { + if (!AllChildrenDone()) + client_->Disconnect(); + OnStateChange(buzz::XmppEngine::STATE_CLOSED); +} + +void XmppPump::OnStateChange(buzz::XmppEngine::State state) { + if (state_ == state) + return; + state_ = state; + if (notify_ != NULL) + notify_->OnStateChange(state); +} + +void XmppPump::WakeTasks() { + rtc::Thread::Current()->Post(RTC_FROM_HERE, this); +} + +int64_t XmppPump::CurrentTime() { + return (int64_t)rtc::TimeMillis(); +} + +void XmppPump::OnMessage(rtc::Message *pmsg) { + RunTasks(); +} + +buzz::XmppReturnStatus XmppPump::SendStanza(const buzz::XmlElement *stanza) { + if (!AllChildrenDone()) + return client_->SendStanza(stanza); + return buzz::XMPP_RETURN_BADSTATE; +} + +} // namespace buzz + diff --git a/webrtc/libjingle/xmpp/xmpppump.h b/webrtc/libjingle/xmpp/xmpppump.h new file mode 100644 index 0000000000..bd1b5628b5 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpppump.h @@ -0,0 +1,62 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPPUMP_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPPUMP_H_ + +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/taskrunner.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace buzz { + +// Simple xmpp pump + +class XmppPumpNotify { +public: + virtual ~XmppPumpNotify() {} + virtual void OnStateChange(buzz::XmppEngine::State state) = 0; +}; + +class XmppPump : public rtc::MessageHandler, public rtc::TaskRunner { +public: + XmppPump(buzz::XmppPumpNotify * notify = NULL); + + buzz::XmppClient *client() { return client_; } + + void DoLogin(const buzz::XmppClientSettings & xcs, + buzz::AsyncSocket* socket, + buzz::PreXmppAuth* auth); + void DoDisconnect(); + + void OnStateChange(buzz::XmppEngine::State state); + + void WakeTasks(); + + int64_t CurrentTime(); + + void OnMessage(rtc::Message *pmsg); + + buzz::XmppReturnStatus SendStanza(const buzz::XmlElement *stanza); + +private: + buzz::XmppClient *client_; + buzz::XmppEngine::State state_; + buzz::XmppPumpNotify *notify_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPPUMP_H_ + diff --git a/webrtc/libjingle/xmpp/xmppsocket.cc b/webrtc/libjingle/xmpp/xmppsocket.cc new file mode 100644 index 0000000000..c42dcd128e --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppsocket.cc @@ -0,0 +1,246 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "xmppsocket.h" + +#include +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" +#ifdef FEATURE_ENABLE_SSL +#include "webrtc/base/ssladapter.h" +#endif + +#ifdef USE_SSLSTREAM +#include "webrtc/base/socketstream.h" +#ifdef FEATURE_ENABLE_SSL +#include "webrtc/base/sslstreamadapter.h" +#endif // FEATURE_ENABLE_SSL +#endif // USE_SSLSTREAM + +namespace buzz { + +XmppSocket::XmppSocket(buzz::TlsOptions tls) : cricket_socket_(NULL), + tls_(tls) { + state_ = buzz::AsyncSocket::STATE_CLOSED; +} + +void XmppSocket::CreateCricketSocket(int family) { + rtc::Thread* pth = rtc::Thread::Current(); + if (family == AF_UNSPEC) { + family = AF_INET; + } + rtc::AsyncSocket* socket = + pth->socketserver()->CreateAsyncSocket(family, SOCK_STREAM); +#ifndef USE_SSLSTREAM +#ifdef FEATURE_ENABLE_SSL + if (tls_ != buzz::TLS_DISABLED) { + socket = rtc::SSLAdapter::Create(socket); + } +#endif // FEATURE_ENABLE_SSL + cricket_socket_ = socket; + cricket_socket_->SignalReadEvent.connect(this, &XmppSocket::OnReadEvent); + cricket_socket_->SignalWriteEvent.connect(this, &XmppSocket::OnWriteEvent); + cricket_socket_->SignalConnectEvent.connect(this, + &XmppSocket::OnConnectEvent); + cricket_socket_->SignalCloseEvent.connect(this, &XmppSocket::OnCloseEvent); +#else // USE_SSLSTREAM + cricket_socket_ = socket; + stream_ = new rtc::SocketStream(cricket_socket_); +#ifdef FEATURE_ENABLE_SSL + if (tls_ != buzz::TLS_DISABLED) + stream_ = rtc::SSLStreamAdapter::Create(stream_); +#endif // FEATURE_ENABLE_SSL + stream_->SignalEvent.connect(this, &XmppSocket::OnEvent); +#endif // USE_SSLSTREAM +} + +XmppSocket::~XmppSocket() { + Close(); +#ifndef USE_SSLSTREAM + delete cricket_socket_; +#else // USE_SSLSTREAM + delete stream_; +#endif // USE_SSLSTREAM +} + +#ifndef USE_SSLSTREAM +void XmppSocket::OnReadEvent(rtc::AsyncSocket * socket) { + SignalRead(); +} + +void XmppSocket::OnWriteEvent(rtc::AsyncSocket * socket) { + // Write bytes if there are any + while (buffer_.size() > 0) { + int written = cricket_socket_->Send(buffer_.data(), buffer_.size()); + if (written > 0) { + ASSERT(static_cast(written) <= buffer_.size()); + memmove(buffer_.data(), buffer_.data() + written, + buffer_.size() - written); + buffer_.SetSize(buffer_.size() - written); + continue; + } + if (!cricket_socket_->IsBlocking()) + LOG(LS_ERROR) << "Send error: " << cricket_socket_->GetError(); + return; + } +} + +void XmppSocket::OnConnectEvent(rtc::AsyncSocket * socket) { +#if defined(FEATURE_ENABLE_SSL) + if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) { + state_ = buzz::AsyncSocket::STATE_TLS_OPEN; + SignalSSLConnected(); + OnWriteEvent(cricket_socket_); + return; + } +#endif // !defined(FEATURE_ENABLE_SSL) + state_ = buzz::AsyncSocket::STATE_OPEN; + SignalConnected(); +} + +void XmppSocket::OnCloseEvent(rtc::AsyncSocket * socket, int error) { + SignalCloseEvent(error); +} + +#else // USE_SSLSTREAM + +void XmppSocket::OnEvent(rtc::StreamInterface* stream, + int events, int err) { + if ((events & rtc::SE_OPEN)) { +#if defined(FEATURE_ENABLE_SSL) + if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) { + state_ = buzz::AsyncSocket::STATE_TLS_OPEN; + SignalSSLConnected(); + events |= rtc::SE_WRITE; + } else +#endif + { + state_ = buzz::AsyncSocket::STATE_OPEN; + SignalConnected(); + } + } + if ((events & rtc::SE_READ)) + SignalRead(); + if ((events & rtc::SE_WRITE)) { + // Write bytes if there are any + while (buffer_.size() > 0) { + rtc::StreamResult result; + size_t written; + int error; + result = stream_->Write(buffer_.data(), buffer_.size(), + &written, &error); + if (result == rtc::SR_ERROR) { + LOG(LS_ERROR) << "Send error: " << error; + return; + } + if (result == rtc::SR_BLOCK) + return; + ASSERT(result == rtc::SR_SUCCESS); + ASSERT(written > 0); + ASSERT(written <= buffer_.size()); + memmove(buffer_.data(), buffer_.data() + written, + buffer_.size() - written); + buffer_.SetSize(buffer_.size() - written); + } + } + if ((events & rtc::SE_CLOSE)) + SignalCloseEvent(err); +} +#endif // USE_SSLSTREAM + +buzz::AsyncSocket::State XmppSocket::state() { + return state_; +} + +buzz::AsyncSocket::Error XmppSocket::error() { + return buzz::AsyncSocket::ERROR_NONE; +} + +int XmppSocket::GetError() { + return 0; +} + +bool XmppSocket::Connect(const rtc::SocketAddress& addr) { + if (cricket_socket_ == NULL) { + CreateCricketSocket(addr.family()); + } + if (cricket_socket_->Connect(addr) < 0) { + return cricket_socket_->IsBlocking(); + } + return true; +} + +bool XmppSocket::Read(char * data, size_t len, size_t* len_read) { +#ifndef USE_SSLSTREAM + int read = cricket_socket_->Recv(data, len, nullptr); + if (read > 0) { + *len_read = (size_t)read; + return true; + } +#else // USE_SSLSTREAM + rtc::StreamResult result = stream_->Read(data, len, len_read, NULL); + if (result == rtc::SR_SUCCESS) + return true; +#endif // USE_SSLSTREAM + return false; +} + +bool XmppSocket::Write(const char * data, size_t len) { + buffer_.AppendData(data, len); +#ifndef USE_SSLSTREAM + OnWriteEvent(cricket_socket_); +#else // USE_SSLSTREAM + OnEvent(stream_, rtc::SE_WRITE, 0); +#endif // USE_SSLSTREAM + return true; +} + +bool XmppSocket::Close() { + if (state_ != buzz::AsyncSocket::STATE_OPEN) + return false; +#ifndef USE_SSLSTREAM + if (cricket_socket_->Close() == 0) { + state_ = buzz::AsyncSocket::STATE_CLOSED; + SignalClosed(); + return true; + } + return false; +#else // USE_SSLSTREAM + state_ = buzz::AsyncSocket::STATE_CLOSED; + stream_->Close(); + SignalClosed(); + return true; +#endif // USE_SSLSTREAM +} + +bool XmppSocket::StartTls(const std::string & domainname) { +#if defined(FEATURE_ENABLE_SSL) + if (tls_ == buzz::TLS_DISABLED) + return false; +#ifndef USE_SSLSTREAM + rtc::SSLAdapter* ssl_adapter = + static_cast(cricket_socket_); + if (ssl_adapter->StartSSL(domainname.c_str(), false) != 0) + return false; +#else // USE_SSLSTREAM + rtc::SSLStreamAdapter* ssl_stream = + static_cast(stream_); + if (ssl_stream->StartSSLWithServer(domainname.c_str()) != 0) + return false; +#endif // USE_SSLSTREAM + state_ = buzz::AsyncSocket::STATE_TLS_CONNECTING; + return true; +#else // !defined(FEATURE_ENABLE_SSL) + return false; +#endif // !defined(FEATURE_ENABLE_SSL) +} + +} // namespace buzz + diff --git a/webrtc/libjingle/xmpp/xmppsocket.h b/webrtc/libjingle/xmpp/xmppsocket.h new file mode 100644 index 0000000000..02d645383c --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppsocket.h @@ -0,0 +1,72 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPSOCKET_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPSOCKET_H_ + +#include "webrtc/libjingle/xmpp/asyncsocket.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/buffer.h" +#include "webrtc/base/sigslot.h" + +// The below define selects the SSLStreamAdapter implementation for +// SSL, as opposed to the SSLAdapter socket adapter. +// #define USE_SSLSTREAM + +namespace rtc { + class StreamInterface; + class SocketAddress; +}; +extern rtc::AsyncSocket* cricket_socket_; + +namespace buzz { + +class XmppSocket : public buzz::AsyncSocket, public sigslot::has_slots<> { +public: + XmppSocket(buzz::TlsOptions tls); + ~XmppSocket(); + + virtual buzz::AsyncSocket::State state(); + virtual buzz::AsyncSocket::Error error(); + virtual int GetError(); + + virtual bool Connect(const rtc::SocketAddress& addr); + virtual bool Read(char * data, size_t len, size_t* len_read); + virtual bool Write(const char * data, size_t len); + virtual bool Close(); + virtual bool StartTls(const std::string & domainname); + + sigslot::signal1 SignalCloseEvent; + +private: + void CreateCricketSocket(int family); +#ifndef USE_SSLSTREAM + void OnReadEvent(rtc::AsyncSocket * socket); + void OnWriteEvent(rtc::AsyncSocket * socket); + void OnConnectEvent(rtc::AsyncSocket * socket); + void OnCloseEvent(rtc::AsyncSocket * socket, int error); +#else // USE_SSLSTREAM + void OnEvent(rtc::StreamInterface* stream, int events, int err); +#endif // USE_SSLSTREAM + + rtc::AsyncSocket * cricket_socket_; +#ifdef USE_SSLSTREAM + rtc::StreamInterface *stream_; +#endif // USE_SSLSTREAM + buzz::AsyncSocket::State state_; + rtc::Buffer buffer_; + buzz::TlsOptions tls_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPSOCKET_H_ + diff --git a/webrtc/libjingle/xmpp/xmppstanzaparser.cc b/webrtc/libjingle/xmpp/xmppstanzaparser.cc new file mode 100644 index 0000000000..035bb0b6f1 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppstanzaparser.cc @@ -0,0 +1,89 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmppstanzaparser.h" + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/common.h" +#ifdef EXPAT_RELATIVE_PATH +#include "expat.h" +#else +#include "third_party/expat/v2_0_1/Source/lib/expat.h" +#endif + +namespace buzz { + +XmppStanzaParser::XmppStanzaParser(XmppStanzaParseHandler *psph) : + psph_(psph), + innerHandler_(this), + parser_(&innerHandler_), + depth_(0), + builder_() { +} + +void +XmppStanzaParser::Reset() { + parser_.Reset(); + depth_ = 0; + builder_.Reset(); +} + +void +XmppStanzaParser::IncomingStartElement( + XmlParseContext * pctx, const char * name, const char ** atts) { + if (depth_++ == 0) { + XmlElement * pelStream = XmlBuilder::BuildElement(pctx, name, atts); + if (pelStream == NULL) { + pctx->RaiseError(XML_ERROR_SYNTAX); + return; + } + psph_->StartStream(pelStream); + delete pelStream; + return; + } + + builder_.StartElement(pctx, name, atts); +} + +void +XmppStanzaParser::IncomingCharacterData( + XmlParseContext * pctx, const char * text, int len) { + if (depth_ > 1) { + builder_.CharacterData(pctx, text, len); + } +} + +void +XmppStanzaParser::IncomingEndElement( + XmlParseContext * pctx, const char * name) { + if (--depth_ == 0) { + psph_->EndStream(); + return; + } + + builder_.EndElement(pctx, name); + + if (depth_ == 1) { + XmlElement *element = builder_.CreateElement(); + psph_->Stanza(element); + delete element; + } +} + +void +XmppStanzaParser::IncomingError( + XmlParseContext * pctx, XML_Error errCode) { + RTC_UNUSED(pctx); + RTC_UNUSED(errCode); + psph_->XmlError(); +} + +} diff --git a/webrtc/libjingle/xmpp/xmppstanzaparser.h b/webrtc/libjingle/xmpp/xmppstanzaparser.h new file mode 100644 index 0000000000..3ad052e4ff --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppstanzaparser.h @@ -0,0 +1,80 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPSTANZAPARSER_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPSTANZAPARSER_H_ + +#include "webrtc/libjingle/xmllite/xmlbuilder.h" +#include "webrtc/libjingle/xmllite/xmlparser.h" + + +namespace buzz { + +class XmlElement; + +class XmppStanzaParseHandler { +public: + virtual ~XmppStanzaParseHandler() {} + virtual void StartStream(const XmlElement * pelStream) = 0; + virtual void Stanza(const XmlElement * pelStanza) = 0; + virtual void EndStream() = 0; + virtual void XmlError() = 0; +}; + +class XmppStanzaParser { +public: + XmppStanzaParser(XmppStanzaParseHandler *psph); + bool Parse(const char * data, size_t len, bool isFinal) + { return parser_.Parse(data, len, isFinal); } + void Reset(); + +private: + class ParseHandler : public XmlParseHandler { + public: + ParseHandler(XmppStanzaParser * outer) : outer_(outer) {} + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) + { outer_->IncomingStartElement(pctx, name, atts); } + virtual void EndElement(XmlParseContext * pctx, + const char * name) + { outer_->IncomingEndElement(pctx, name); } + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len) + { outer_->IncomingCharacterData(pctx, text, len); } + virtual void Error(XmlParseContext * pctx, + XML_Error errCode) + { outer_->IncomingError(pctx, errCode); } + private: + XmppStanzaParser * const outer_; + }; + + friend class ParseHandler; + + void IncomingStartElement(XmlParseContext * pctx, + const char * name, const char ** atts); + void IncomingEndElement(XmlParseContext * pctx, + const char * name); + void IncomingCharacterData(XmlParseContext * pctx, + const char * text, int len); + void IncomingError(XmlParseContext * pctx, + XML_Error errCode); + + XmppStanzaParseHandler * psph_; + ParseHandler innerHandler_; + XmlParser parser_; + int depth_; + XmlBuilder builder_; + + }; + + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPSTANZAPARSER_H_ diff --git a/webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc b/webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc new file mode 100644 index 0000000000..65fcac9d63 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc @@ -0,0 +1,175 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/xmppstanzaparser.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::QName; +using buzz::XmlElement; +using buzz::XmppStanzaParser; +using buzz::XmppStanzaParseHandler; + +class XmppStanzaParserTestHandler : public XmppStanzaParseHandler { + public: + virtual void StartStream(const XmlElement * element) { + ss_ << "START" << element->Str(); + } + virtual void Stanza(const XmlElement * element) { + ss_ << "STANZA" << element->Str(); + } + virtual void EndStream() { + ss_ << "END"; + } + virtual void XmlError() { + ss_ << "ERROR"; + } + + std::string Str() { + return ss_.str(); + } + + std::string StrClear() { + std::string result = ss_.str(); + ss_.str(""); + return result; + } + + private: + std::stringstream ss_; +}; + + +TEST(XmppStanzaParserTest, TestTrivial) { + XmppStanzaParserTestHandler handler; + XmppStanzaParser parser(&handler); + std::string fragment; + + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("STARTEND", handler.StrClear()); +} + +TEST(XmppStanzaParserTest, TestStanzaAtATime) { + XmppStanzaParserTestHandler handler; + XmppStanzaParser parser(&handler); + std::string fragment; + + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START", handler.StrClear()); + + fragment = "hello"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("STANZA" + "hello", handler.StrClear()); + + fragment = " SOME TEXT TO IGNORE "; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("", handler.StrClear()); + + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("STANZA" + "", handler.StrClear()); + + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("END", handler.StrClear()); +} + +TEST(XmppStanzaParserTest, TestFragmentedStanzas) { + XmppStanzaParserTestHandler handler; + XmppStanzaParser parser(&handler); + std::string fragment; + + fragment = "", handler.StrClear()); + + fragment = "lo IGNORE ME " + "" + "helloSTANZA", handler.StrClear()); + + fragment = "ream:stream>"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("END", handler.StrClear()); +} + +TEST(XmppStanzaParserTest, TestReset) { + XmppStanzaParserTestHandler handler; + XmppStanzaParser parser(&handler); + std::string fragment; + + fragment = "", handler.StrClear()); + parser.Reset(); + + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START", handler.StrClear()); + + fragment = "hello"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("STANZA" + "hello", handler.StrClear()); +} + +TEST(XmppStanzaParserTest, TestError) { + XmppStanzaParserTestHandler handler; + XmppStanzaParser parser(&handler); + std::string fragment; + + fragment = "<-foobar/>"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("ERROR", handler.StrClear()); + + parser.Reset(); + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("ERROR", handler.StrClear()); + parser.Reset(); + + fragment = "ns:stream='str'>hel"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("ERROR", handler.StrClear()); + parser.Reset(); + + fragment = "" + ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("STARTSTANZA" + "ERROR", handler.StrClear()); +} diff --git a/webrtc/libjingle/xmpp/xmpptask.cc b/webrtc/libjingle/xmpp/xmpptask.cc new file mode 100644 index 0000000000..84f9ba6b92 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpptask.cc @@ -0,0 +1,158 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +XmppClientInterface::XmppClientInterface() { +} + +XmppClientInterface::~XmppClientInterface() { +} + +XmppTask::XmppTask(XmppTaskParentInterface* parent, + XmppEngine::HandlerLevel level) + : XmppTaskBase(parent), stopped_(false) { +#if !defined(NDEBUG) + debug_force_timeout_ = false; +#endif + + id_ = GetClient()->NextId(); + GetClient()->AddXmppTask(this, level); + GetClient()->SignalDisconnected.connect(this, &XmppTask::OnDisconnect); +} + +XmppTask::~XmppTask() { + StopImpl(); +} + +void XmppTask::StopImpl() { + while (NextStanza() != NULL) {} + if (!stopped_) { + GetClient()->RemoveXmppTask(this); + GetClient()->SignalDisconnected.disconnect(this); + stopped_ = true; + } +} + +XmppReturnStatus XmppTask::SendStanza(const XmlElement* stanza) { + if (stopped_) + return XMPP_RETURN_BADSTATE; + return GetClient()->SendStanza(stanza); +} + +XmppReturnStatus XmppTask::SendStanzaError(const XmlElement* element_original, + XmppStanzaError code, + const std::string& text) { + if (stopped_) + return XMPP_RETURN_BADSTATE; + return GetClient()->SendStanzaError(element_original, code, text); +} + +void XmppTask::Stop() { + StopImpl(); + Task::Stop(); +} + +void XmppTask::OnDisconnect() { + Error(); +} + +void XmppTask::QueueStanza(const XmlElement* stanza) { +#if !defined(NDEBUG) + if (debug_force_timeout_) + return; +#endif + + stanza_queue_.push_back(new XmlElement(*stanza)); + Wake(); +} + +const XmlElement* XmppTask::NextStanza() { + XmlElement* result = NULL; + if (!stanza_queue_.empty()) { + result = stanza_queue_.front(); + stanza_queue_.pop_front(); + } + next_stanza_.reset(result); + return result; +} + +XmlElement* XmppTask::MakeIq(const std::string& type, + const buzz::Jid& to, + const std::string& id) { + XmlElement* result = new XmlElement(QN_IQ); + if (!type.empty()) + result->AddAttr(QN_TYPE, type); + if (!to.IsEmpty()) + result->AddAttr(QN_TO, to.Str()); + if (!id.empty()) + result->AddAttr(QN_ID, id); + return result; +} + +XmlElement* XmppTask::MakeIqResult(const XmlElement * query) { + XmlElement* result = new XmlElement(QN_IQ); + result->AddAttr(QN_TYPE, STR_RESULT); + if (query->HasAttr(QN_FROM)) { + result->AddAttr(QN_TO, query->Attr(QN_FROM)); + } + result->AddAttr(QN_ID, query->Attr(QN_ID)); + return result; +} + +bool XmppTask::MatchResponseIq(const XmlElement* stanza, + const Jid& to, + const std::string& id) { + if (stanza->Name() != QN_IQ) + return false; + + if (stanza->Attr(QN_ID) != id) + return false; + + return MatchStanzaFrom(stanza, to); +} + +bool XmppTask::MatchStanzaFrom(const XmlElement* stanza, + const Jid& to) { + Jid from(stanza->Attr(QN_FROM)); + if (from == to) + return true; + + // We address the server as "", check if we are doing so here. + if (!to.IsEmpty()) + return false; + + // It is legal for the server to identify itself with "domain" or + // "myself@domain" + Jid me = GetClient()->jid(); + return (from == Jid(me.domain())) || (from == me.BareJid()); +} + +bool XmppTask::MatchRequestIq(const XmlElement* stanza, + const std::string& type, + const QName& qn) { + if (stanza->Name() != QN_IQ) + return false; + + if (stanza->Attr(QN_TYPE) != type) + return false; + + if (stanza->FirstNamed(qn) == NULL) + return false; + + return true; +} + +} diff --git a/webrtc/libjingle/xmpp/xmpptask.h b/webrtc/libjingle/xmpp/xmpptask.h new file mode 100644 index 0000000000..3f3a39c791 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpptask.h @@ -0,0 +1,175 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPTASK_H_ + +#include +#include +#include + +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/task.h" +#include "webrtc/base/taskparent.h" + +namespace buzz { + +///////////////////////////////////////////////////////////////////// +// +// XMPPTASK +// +///////////////////////////////////////////////////////////////////// +// +// See Task and XmppClient first. +// +// XmppTask is a task that is designed to go underneath XmppClient and be +// useful there. It has a way of finding its XmppClient parent so you +// can have it nested arbitrarily deep under an XmppClient and it can +// still find the XMPP services. +// +// Tasks register themselves to listen to particular kinds of stanzas +// that are sent out by the client. Rather than processing stanzas +// right away, they should decide if they own the sent stanza, +// and if so, queue it and Wake() the task, or if a stanza does not belong +// to you, return false right away so the next XmppTask can take a crack. +// This technique (synchronous recognize, but asynchronous processing) +// allows you to have arbitrary logic for recognizing stanzas yet still, +// for example, disconnect a client while processing a stanza - +// without reentrancy problems. +// +///////////////////////////////////////////////////////////////////// + +class XmppTask; + +// XmppClientInterface is an abstract interface for sending and +// handling stanzas. It can be implemented for unit tests or +// different network environments. It will usually be implemented by +// XmppClient. +class XmppClientInterface { + public: + XmppClientInterface(); + virtual ~XmppClientInterface(); + + virtual XmppEngine::State GetState() const = 0; + virtual const Jid& jid() const = 0; + virtual std::string NextId() = 0; + virtual XmppReturnStatus SendStanza(const XmlElement* stanza) = 0; + virtual XmppReturnStatus SendStanzaError(const XmlElement* original_stanza, + XmppStanzaError error_code, + const std::string& message) = 0; + virtual void AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) = 0; + virtual void RemoveXmppTask(XmppTask* task) = 0; + sigslot::signal0<> SignalDisconnected; + + RTC_DISALLOW_COPY_AND_ASSIGN(XmppClientInterface); +}; + +// XmppTaskParentInterface is the interface require for any parent of +// an XmppTask. It needs, for example, a way to get an +// XmppClientInterface. + +// We really ought to inherit from a TaskParentInterface, but we tried +// that and it's way too complicated to change +// Task/TaskParent/TaskRunner. For now, this works. +class XmppTaskParentInterface : public rtc::Task { + public: + explicit XmppTaskParentInterface(rtc::TaskParent* parent) + : Task(parent) { + } + virtual ~XmppTaskParentInterface() {} + + virtual XmppClientInterface* GetClient() = 0; + + RTC_DISALLOW_COPY_AND_ASSIGN(XmppTaskParentInterface); +}; + +class XmppTaskBase : public XmppTaskParentInterface { + public: + explicit XmppTaskBase(XmppTaskParentInterface* parent) + : XmppTaskParentInterface(parent), + parent_(parent) { + } + virtual ~XmppTaskBase() {} + + virtual XmppClientInterface* GetClient() { + return parent_->GetClient(); + } + + protected: + XmppTaskParentInterface* parent_; + + RTC_DISALLOW_COPY_AND_ASSIGN(XmppTaskBase); +}; + +class XmppTask : public XmppTaskBase, + public XmppStanzaHandler, + public sigslot::has_slots<> +{ + public: + XmppTask(XmppTaskParentInterface* parent, + XmppEngine::HandlerLevel level = XmppEngine::HL_NONE); + virtual ~XmppTask(); + + std::string task_id() const { return id_; } + void set_task_id(std::string id) { id_ = id; } + +#if !defined(NDEBUG) + void set_debug_force_timeout(const bool f) { debug_force_timeout_ = f; } +#endif + + virtual bool HandleStanza(const XmlElement* stanza) { return false; } + + protected: + XmppReturnStatus SendStanza(const XmlElement* stanza); + XmppReturnStatus SetResult(const std::string& code); + XmppReturnStatus SendStanzaError(const XmlElement* element_original, + XmppStanzaError code, + const std::string& text); + + virtual void Stop(); + virtual void OnDisconnect(); + + virtual void QueueStanza(const XmlElement* stanza); + const XmlElement* NextStanza(); + + bool MatchStanzaFrom(const XmlElement* stanza, const Jid& match_jid); + + bool MatchResponseIq(const XmlElement* stanza, const Jid& to, + const std::string& task_id); + + static bool MatchRequestIq(const XmlElement* stanza, const std::string& type, + const QName& qn); + static XmlElement *MakeIqResult(const XmlElement* query); + static XmlElement *MakeIq(const std::string& type, + const Jid& to, const std::string& task_id); + + // Returns true if the task is under the specified rate limit and updates the + // rate limit accordingly + bool VerifyTaskRateLimit(const std::string task_name, int max_count, + int per_x_seconds); + +private: + void StopImpl(); + + bool stopped_; + std::deque stanza_queue_; + std::unique_ptr next_stanza_; + std::string id_; + +#if !defined(NDEBUG) + bool debug_force_timeout_; +#endif +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPTASK_H_ diff --git a/webrtc/libjingle/xmpp/xmppthread.cc b/webrtc/libjingle/xmpp/xmppthread.cc new file mode 100644 index 0000000000..0fd5002d62 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppthread.cc @@ -0,0 +1,69 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmppthread.h" + +#include "webrtc/libjingle/xmpp/xmppauth.h" +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" + +namespace buzz { +namespace { + +const uint32_t MSG_LOGIN = 1; +const uint32_t MSG_DISCONNECT = 2; + +struct LoginData: public rtc::MessageData { + LoginData(const buzz::XmppClientSettings& s) : xcs(s) {} + virtual ~LoginData() {} + + buzz::XmppClientSettings xcs; +}; + +} // namespace + +XmppThread::XmppThread() { + pump_ = new buzz::XmppPump(this); +} + +XmppThread::~XmppThread() { + Stop(); + delete pump_; +} + +void XmppThread::ProcessMessages(int cms) { + rtc::Thread::ProcessMessages(cms); +} + +void XmppThread::Login(const buzz::XmppClientSettings& xcs) { + Post(RTC_FROM_HERE, this, MSG_LOGIN, new LoginData(xcs)); +} + +void XmppThread::Disconnect() { + Post(RTC_FROM_HERE, this, MSG_DISCONNECT); +} + +void XmppThread::OnStateChange(buzz::XmppEngine::State state) { +} + +void XmppThread::OnMessage(rtc::Message* pmsg) { + if (pmsg->message_id == MSG_LOGIN) { + ASSERT(pmsg->pdata != NULL); + LoginData* data = reinterpret_cast(pmsg->pdata); + pump_->DoLogin(data->xcs, new XmppSocket(buzz::TLS_DISABLED), + new XmppAuth()); + delete data; + } else if (pmsg->message_id == MSG_DISCONNECT) { + pump_->DoDisconnect(); + } else { + ASSERT(false); + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/xmppthread.h b/webrtc/libjingle/xmpp/xmppthread.h new file mode 100644 index 0000000000..8017925049 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppthread.h @@ -0,0 +1,45 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ + +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpppump.h" +#include "webrtc/libjingle/xmpp/xmppsocket.h" +#include "webrtc/base/thread.h" + +namespace buzz { + +class XmppThread: + public rtc::Thread, buzz::XmppPumpNotify, rtc::MessageHandler { +public: + XmppThread(); + ~XmppThread(); + + buzz::XmppClient* client() { return pump_->client(); } + + void ProcessMessages(int cms); + + void Login(const buzz::XmppClientSettings & xcs); + void Disconnect(); + +private: + buzz::XmppPump* pump_; + + void OnStateChange(buzz::XmppEngine::State state); + void OnMessage(rtc::Message* pmsg); +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ +