tokio-xmpp/.gitignore π
@@ -0,0 +1 @@
+target
Maxime βpepβ Buquet created
tokio-xmpp/.gitignore | 1
tokio-xmpp/.gitlab-ci.yml | 14
tokio-xmpp/Cargo.lock | 1731 +++++++++++++++++++++++++++
tokio-xmpp/Cargo.toml | 28
tokio-xmpp/README.md | 6
tokio-xmpp/examples/contact_addr.rs | 130 ++
tokio-xmpp/examples/download_avatars.rs | 232 +++
tokio-xmpp/examples/echo_bot.rs | 106 +
tokio-xmpp/examples/echo_component.rs | 99 +
tokio-xmpp/logo.svg | 126 +
tokio-xmpp/src/client/auth.rs | 116 +
tokio-xmpp/src/client/bind.rs | 102 +
tokio-xmpp/src/client/mod.rs | 236 +++
tokio-xmpp/src/component/auth.rs | 89 +
tokio-xmpp/src/component/mod.rs | 163 ++
tokio-xmpp/src/error.rs | 224 +++
tokio-xmpp/src/event.rs | 54
tokio-xmpp/src/happy_eyeballs.rs | 196 +++
tokio-xmpp/src/lib.rs | 19
tokio-xmpp/src/starttls.rs | 114 +
tokio-xmpp/src/stream_start.rs | 125 +
tokio-xmpp/src/xmpp_codec.rs | 532 ++++++++
tokio-xmpp/src/xmpp_stream.rs | 92 +
23 files changed, 4,535 insertions(+)
@@ -0,0 +1 @@
+target
@@ -0,0 +1,14 @@
+stages:
+ - build
+rust-latest:
+ stage: build
+ image: rust:latest
+ script:
+ - cargo build --verbose
+ - cargo test --verbose
+rust-nightly:
+ stage: build
+ image: rustlang/rust:nightly
+ script:
+ - cargo build --verbose
+ - cargo test --verbose
@@ -0,0 +1,1731 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "arrayvec"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "autocfg"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "backtrace"
+version = "0.3.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "backtrace-sys"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "base64"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "blake2"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "byteorder"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bytes"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "c2-chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "chrono"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "enum-as-inner"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "failure"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "failure_derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "fnv"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "futf"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures"
+version = "0.1.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "generic-array"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "hmac"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "hostname"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ipconfig"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "jid"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "minidom 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "lock_api"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "log"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lru-cache"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "markup5ever"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "string_cache 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "string_cache_codegen 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tendril 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memchr"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memoffset"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "minidom"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quick-xml 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mio"
+version = "0.6.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mio-uds"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.9.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "nodrop"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num-integer"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "openssl"
+version = "0.10.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.9.51 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "phf"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "proc-macro2"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "quick-xml"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quote"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_isaac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_jitter"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_os"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "resolv-conf"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "sasl"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "security-framework"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde_derive"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sha2"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sha3"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "slab"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "smallvec"
+version = "0.6.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "socket2"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "string_cache"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
+ "string_cache_codegen 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "string_cache_shared"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "subtle"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "syn"
+version = "0.15.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
+ "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tendril"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "time"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-udp 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-codec"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-current-thread"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-executor"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-fs"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-io"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-reactor"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-sync"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-tcp"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-threadpool"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-timer"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-tls"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-udp"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-uds"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-xmpp"
+version = "1.0.1"
+dependencies = [
+ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quick-xml 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sasl 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "trust-dns-proto 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "trust-dns-resolver 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "xml5ever 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "xmpp-parsers 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "trust-dns-proto"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "enum-as-inner 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-udp 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "trust-dns-resolver"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ipconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "resolv-conf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-udp 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "trust-dns-proto 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "typenum"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "url"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "wasi"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "widestring"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winreg"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winutil"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "xml5ever"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "markup5ever 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "xmpp-parsers"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "blake2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "jid 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "minidom 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[metadata]
+"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
+"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875"
+"checksum backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5"
+"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
+"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
+"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+"checksum blake2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330"
+"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+"checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09"
+"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
+"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
+"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101"
+"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be"
+"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+"checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68"
+"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
+"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d"
+"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
+"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71"
+"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9"
+"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"
+"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
+"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
+"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+"checksum enum-as-inner 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3d58266c97445680766be408285e798d3401c6d4c378ec5552e78737e681e37d"
+"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
+"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
+"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
+"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+"checksum futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
+"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
+"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
+"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571"
+"checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
+"checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
+"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
+"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+"checksum ipconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa79fa216fbe60834a9c0737d7fcd30425b32d1c58854663e24d4c4b328ed83f"
+"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
+"checksum jid 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "32b4cfc8edfd18c386be7b4e7307e59471aed5e21cd8b80e1aaf070b42de163d"
+"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
+"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+"checksum libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)" = "74dfca3d9957906e8d1e6a0b641dc9a59848e793f1da2165889fd4f62d10d79c"
+"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
+"checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc"
+"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
+"checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+"checksum mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+"checksum markup5ever 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "65381d9d47506b8592b97c4efd936afcf673b09b059f2bef39c7211ee78b9d03"
+"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
+"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
+"checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f"
+"checksum minidom 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "df1f29c97815c4328d6b533d2b8976e80cbed02d97063970aa4a80c100ecf29a"
+"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23"
+"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
+"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
+"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
+"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
+"checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30"
+"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
+"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
+"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
+"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273"
+"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+"checksum openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2f372b2b53ce10fb823a337aaa674e3a7d072b957c6264d0f4ff0bd86e657449"
+"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
+"checksum openssl-sys 0.9.51 (registry+https://github.com/rust-lang/crates.io-index)" = "ba24190c8f0805d3bd2ce028f439fe5af1d55882bbe6261bed1dbc93b50dd6b1"
+"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
+"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
+"checksum pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9"
+"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+"checksum phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
+"checksum phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e"
+"checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"
+"checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
+"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea"
+"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
+"checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
+"checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0"
+"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
+"checksum quick-xml 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cd45021132c1cb5540995e93fcc2cf5a874ef84f9639168fb6819caa023d4be"
+"checksum quick-xml 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcdba8c8d71275493d966ef052a88726ac8590c15a09968b32158205c672ef"
+"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
+"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
+"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
+"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
+"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
+"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
+"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
+"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
+"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
+"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
+"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
+"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
+"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
+"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
+"checksum resolv-conf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b263b4aa1b5de9ffc0054a2386f96992058bb6870aab516f8cdeb8a667d56dcb"
+"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
+"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
+"checksum sasl 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e457758c85b736bbad56dc099406cd2a9c19554cf81880dba7a51d092929e600"
+"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021"
+"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
+"checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2"
+"checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56"
+"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd"
+"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
+"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
+"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68"
+"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
+"checksum sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf"
+"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
+"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
+"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85"
+"checksum string_cache 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "96ccb3a75a3caf2d7f2eb9ada86ec1fbbd4c74ad2bd8dc00a96a0c2f93509ef0"
+"checksum string_cache_codegen 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f45ed1b65bf9a4bf2f7b7dc59212d1926e9eaf00fa998988e420fd124467c6"
+"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
+"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
+"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
+"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
+"checksum synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203"
+"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
+"checksum tendril 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "707feda9f2582d5d680d733e38755547a3e8fb471e7ba11452ecfd9ce93a5d3b"
+"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
+"checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"
+"checksum tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f"
+"checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443"
+"checksum tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f27ee0e6db01c5f0b2973824547ce7e637b2ed79b891a9677b0de9bd532b6ac"
+"checksum tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe6dc22b08d6993916647d108a1a7d15b9cd29c4f4496c62b92c45b5041b7af"
+"checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926"
+"checksum tokio-reactor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "c56391be9805bc80163151c0b9e5164ee64f4b0200962c346fea12773158f22d"
+"checksum tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d06554cce1ae4a50f42fba8023918afa931413aded705b560e29600ccf7c6d76"
+"checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119"
+"checksum tokio-threadpool 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2bd2c6a3885302581f4401c82af70d792bb9df1700e7437b0aeb4ada94d5388c"
+"checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e"
+"checksum tokio-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "354b8cd83825b3c20217a9dc174d6a0c67441a2fae5c41bcb1ea6679f6ae0f7c"
+"checksum tokio-udp 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f02298505547f73e60f568359ef0d016d5acd6e830ab9bc7c4a5b3403440121b"
+"checksum tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445"
+"checksum trust-dns-proto 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05457ece29839d056d8cb66ec080209d34492b3d2e7e00641b486977be973db9"
+"checksum trust-dns-resolver 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb1b3a41ee784f8da051cd342c6f42a3a75ee45818164acad867eac8f2f85332"
+"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
+"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
+"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426"
+"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61"
+"checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
+"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95"
+"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
+"checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6"
+"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
+"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
+"checksum winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e"
+"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+"checksum xml5ever 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b93ca59bfbbc3c0f807a61faea54e6a4c753f82857d3730e9afb5523b6149c7"
+"checksum xmpp-parsers 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57991f57c0011b66caeed8ed4380489b42c39c41364e31e4bbe011649cb79002"
@@ -0,0 +1,28 @@
+[package]
+name = "tokio-xmpp"
+version = "1.0.1"
+authors = ["Astro <astro@spaceboyz.net>", "Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>", "pep <pep+code@bouah.net>", "O01eg <o01eg@yandex.ru>"]
+description = "Asynchronous XMPP for Rust with tokio"
+license = "MPL-2.0"
+homepage = "https://gitlab.com/xmpp-rs/tokio-xmpp"
+repository = "https://gitlab.com/xmpp-rs/tokio-xmpp"
+documentation = "https://docs.rs/tokio-xmpp"
+categories = ["asynchronous", "network-programming"]
+keywords = ["xmpp", "tokio"]
+edition = "2018"
+
+[dependencies]
+bytes = "0.4"
+futures = "0.1"
+idna = "0.2"
+native-tls = "0.2"
+sasl = "0.4"
+tokio = "0.1"
+tokio-codec = "0.1"
+trust-dns-resolver = "0.12"
+trust-dns-proto = "0.8"
+tokio-io = "0.1"
+tokio-tls = "0.2"
+quick-xml = "0.17"
+xml5ever = "0.15"
+xmpp-parsers = "0.15"
@@ -0,0 +1,6 @@
+# TODO
+
+- [ ] minidom ns
+- [ ] replace debug output with log crate
+- [ ] customize tls verify?
+- [ ] more tests
@@ -0,0 +1,130 @@
+use futures::{future, Sink, Stream};
+use std::convert::TryFrom;
+use std::env::args;
+use std::process::exit;
+use tokio::runtime::current_thread::Runtime;
+use tokio_xmpp::{Client, xmpp_codec::Packet};
+use xmpp_parsers::{
+ Element,
+ Jid,
+ ns,
+ iq::{
+ Iq,
+ IqType,
+ },
+ disco::{
+ DiscoInfoResult,
+ DiscoInfoQuery,
+ },
+ server_info::ServerInfo,
+};
+
+fn main() {
+ let args: Vec<String> = args().collect();
+ if args.len() != 4 {
+ println!("Usage: {} <jid> <password> <target>", args[0]);
+ exit(1);
+ }
+ let jid = &args[1];
+ let password = &args[2];
+ let target = &args[3];
+
+ // tokio_core context
+ let mut rt = Runtime::new().unwrap();
+ // Client instance
+ let client = Client::new(jid, password).unwrap();
+
+ // Make the two interfaces for sending and receiving independent
+ // of each other so we can move one into a closure.
+ let (mut sink, stream) = client.split();
+ // Wrap sink in Option so that we can take() it for the send(self)
+ // to consume and return it back when ready.
+ let mut send = move |packet| {
+ sink.start_send(packet).expect("start_send");
+ };
+ // Main loop, processes events
+ let mut wait_for_stream_end = false;
+ let done = stream.for_each(|event| {
+ if wait_for_stream_end {
+ /* Do Nothing. */
+ } else if event.is_online() {
+ println!("Online!");
+
+ let target_jid: Jid = target.clone().parse().unwrap();
+ let iq = make_disco_iq(target_jid);
+ println!("Sending disco#info request to {}", target.clone());
+ println!(">> {}", String::from(&iq));
+ send(Packet::Stanza(iq));
+ } else if let Some(stanza) = event.into_stanza() {
+ if stanza.is("iq", "jabber:client") {
+ let iq = Iq::try_from(stanza).unwrap();
+ if let IqType::Result(Some(payload)) = iq.payload {
+ if payload.is("query", ns::DISCO_INFO) {
+ if let Ok(disco_info) = DiscoInfoResult::try_from(payload) {
+ for ext in disco_info.extensions {
+ if let Ok(server_info) = ServerInfo::try_from(ext) {
+ print_server_info(server_info);
+ wait_for_stream_end = true;
+ send(Packet::StreamEnd);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Box::new(future::ok(()))
+ });
+
+ // Start polling `done`
+ match rt.block_on(done) {
+ Ok(_) => (),
+ Err(e) => {
+ println!("Fatal: {}", e);
+ ()
+ }
+ }
+}
+
+fn make_disco_iq(target: Jid) -> Element {
+ Iq::from_get("disco", DiscoInfoQuery { node: None })
+ .with_id(String::from("contact"))
+ .with_to(target)
+ .into()
+}
+
+fn convert_field(field: Vec<String>) -> String {
+ field.iter()
+ .fold((field.len(), String::new()), |(l, mut acc), s| {
+ acc.push('<');
+ acc.push_str(&s);
+ acc.push('>');
+ if l > 1 {
+ acc.push(',');
+ acc.push(' ');
+ }
+ (0, acc)
+ }).1
+}
+
+fn print_server_info(server_info: ServerInfo) {
+ if server_info.abuse.len() != 0 {
+ println!("abuse: {}", convert_field(server_info.abuse));
+ }
+ if server_info.admin.len() != 0 {
+ println!("admin: {}", convert_field(server_info.admin));
+ }
+ if server_info.feedback.len() != 0 {
+ println!("feedback: {}", convert_field(server_info.feedback));
+ }
+ if server_info.sales.len() != 0 {
+ println!("sales: {}", convert_field(server_info.sales));
+ }
+ if server_info.security.len() != 0 {
+ println!("security: {}", convert_field(server_info.security));
+ }
+ if server_info.support.len() != 0 {
+ println!("support: {}", convert_field(server_info.support));
+ }
+}
@@ -0,0 +1,232 @@
+use futures::{future, Future, Sink, Stream};
+use std::convert::TryFrom;
+use std::env::args;
+use std::fs::{create_dir_all, File};
+use std::io::{self, Write};
+use std::process::exit;
+use std::str::FromStr;
+use tokio::runtime::current_thread::Runtime;
+use tokio_xmpp::{Client, Packet};
+use xmpp_parsers::{
+ avatar::{Data as AvatarData, Metadata as AvatarMetadata},
+ caps::{compute_disco, hash_caps, Caps},
+ disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity},
+ hashes::Algo,
+ iq::{Iq, IqType},
+ message::Message,
+ ns,
+ presence::{Presence, Type as PresenceType},
+ pubsub::{
+ event::PubSubEvent,
+ pubsub::{Items, PubSub},
+ NodeName,
+ },
+ stanza_error::{StanzaError, ErrorType, DefinedCondition},
+ Jid,
+};
+
+fn main() {
+ let args: Vec<String> = args().collect();
+ if args.len() != 3 {
+ println!("Usage: {} <jid> <password>", args[0]);
+ exit(1);
+ }
+ let jid = &args[1];
+ let password = &args[2];
+
+ // tokio_core context
+ let mut rt = Runtime::new().unwrap();
+ // Client instance
+ let client = Client::new(jid, password).unwrap();
+
+ // Make the two interfaces for sending and receiving independent
+ // of each other so we can move one into a closure.
+ let (sink, stream) = client.split();
+
+ // Create outgoing pipe
+ let (mut tx, rx) = futures::unsync::mpsc::unbounded();
+ rt.spawn(
+ rx.forward(
+ sink.sink_map_err(|_| panic!("Pipe"))
+ )
+ .map(|(rx, mut sink)| {
+ drop(rx);
+ let _ = sink.close();
+ })
+ .map_err(|e| {
+ panic!("Send error: {:?}", e);
+ })
+ );
+
+ let disco_info = make_disco();
+
+ // Main loop, processes events
+ let mut wait_for_stream_end = false;
+ let done = stream.for_each(move |event| {
+ // Helper function to send an iq error.
+ let mut send_error = |to, id, type_, condition, text: &str| {
+ let error = StanzaError::new(type_, condition, "en", text);
+ let iq = Iq::from_error(id, error)
+ .with_to(to);
+ tx.start_send(Packet::Stanza(iq.into())).unwrap();
+ };
+
+ if wait_for_stream_end {
+ /* Do nothing */
+ } else if event.is_online() {
+ println!("Online!");
+
+ let caps = get_disco_caps(&disco_info, "https://gitlab.com/xmpp-rs/tokio-xmpp");
+ let presence = make_presence(caps);
+ tx.start_send(Packet::Stanza(presence.into())).unwrap();
+ } else if let Some(stanza) = event.into_stanza() {
+ if stanza.is("iq", "jabber:client") {
+ let iq = Iq::try_from(stanza).unwrap();
+ if let IqType::Get(payload) = iq.payload {
+ if payload.is("query", ns::DISCO_INFO) {
+ let query = DiscoInfoQuery::try_from(payload);
+ match query {
+ Ok(query) => {
+ let mut disco = disco_info.clone();
+ disco.node = query.node;
+ let iq = Iq::from_result(iq.id, Some(disco))
+ .with_to(iq.from.unwrap());
+ tx.start_send(Packet::Stanza(iq.into())).unwrap();
+ },
+ Err(err) => {
+ send_error(iq.from.unwrap(), iq.id, ErrorType::Modify, DefinedCondition::BadRequest, &format!("{}", err));
+ },
+ }
+ } else {
+ // We MUST answer unhandled get iqs with a service-unavailable error.
+ send_error(iq.from.unwrap(), iq.id, ErrorType::Cancel, DefinedCondition::ServiceUnavailable, "No handler defined for this kind of iq.");
+ }
+ } else if let IqType::Result(Some(payload)) = iq.payload {
+ if payload.is("pubsub", ns::PUBSUB) {
+ let pubsub = PubSub::try_from(payload).unwrap();
+ let from =
+ iq.from.clone().unwrap_or(Jid::from_str(jid).unwrap());
+ handle_iq_result(pubsub, &from);
+ }
+ } else if let IqType::Set(_) = iq.payload {
+ // We MUST answer unhandled set iqs with a service-unavailable error.
+ send_error(iq.from.unwrap(), iq.id, ErrorType::Cancel, DefinedCondition::ServiceUnavailable, "No handler defined for this kind of iq.");
+ }
+ } else if stanza.is("message", "jabber:client") {
+ let message = Message::try_from(stanza).unwrap();
+ let from = message.from.clone().unwrap();
+ if let Some(body) = message.get_best_body(vec!["en"]) {
+ if body.1 .0 == "die" {
+ println!("Secret die command triggered by {}", from);
+ wait_for_stream_end = true;
+ tx.start_send(Packet::StreamEnd).unwrap();
+ }
+ }
+ for child in message.payloads {
+ if child.is("event", ns::PUBSUB_EVENT) {
+ let event = PubSubEvent::try_from(child).unwrap();
+ if let PubSubEvent::PublishedItems { node, items } = event {
+ if node.0 == ns::AVATAR_METADATA {
+ for item in items.into_iter() {
+ let payload = item.payload.clone().unwrap();
+ if payload.is("metadata", ns::AVATAR_METADATA) {
+ // TODO: do something with these metadata.
+ let _metadata = AvatarMetadata::try_from(payload).unwrap();
+ println!(
+ "[1m{}[0m has published an avatar, downloading...",
+ from.clone()
+ );
+ let iq = download_avatar(from.clone());
+ tx.start_send(Packet::Stanza(iq.into())).unwrap();
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if stanza.is("presence", "jabber:client") {
+ // Nothing to do here.
+ } else {
+ panic!("Unknown stanza: {}", String::from(&stanza));
+ }
+ }
+
+ future::ok(())
+ });
+
+ // Start polling `done`
+ match rt.block_on(done) {
+ Ok(_) => (),
+ Err(e) => {
+ println!("Fatal: {}", e);
+ ()
+ }
+ }
+}
+
+fn make_disco() -> DiscoInfoResult {
+ let identities = vec![Identity::new("client", "bot", "en", "tokio-xmpp")];
+ let features = vec![
+ Feature::new(ns::DISCO_INFO),
+ Feature::new(format!("{}+notify", ns::AVATAR_METADATA)),
+ ];
+ DiscoInfoResult {
+ node: None,
+ identities,
+ features,
+ extensions: vec![],
+ }
+}
+
+fn get_disco_caps(disco: &DiscoInfoResult, node: &str) -> Caps {
+ let caps_data = compute_disco(disco);
+ let hash = hash_caps(&caps_data, Algo::Sha_1).unwrap();
+ Caps::new(node, hash)
+}
+
+// Construct a <presence/>
+fn make_presence(caps: Caps) -> Presence {
+ let mut presence = Presence::new(PresenceType::None)
+ .with_priority(-1);
+ presence.set_status("en", "Downloading avatars.");
+ presence.add_payload(caps);
+ presence
+}
+
+fn download_avatar(from: Jid) -> Iq {
+ Iq::from_get("coucou", PubSub::Items(Items {
+ max_items: None,
+ node: NodeName(String::from(ns::AVATAR_DATA)),
+ subid: None,
+ items: Vec::new(),
+ }))
+ .with_to(from)
+}
+
+fn handle_iq_result(pubsub: PubSub, from: &Jid) {
+ if let PubSub::Items(items) = pubsub {
+ if items.node.0 == ns::AVATAR_DATA {
+ for item in items.items {
+ match (item.id.clone(), item.payload.clone()) {
+ (Some(id), Some(payload)) => {
+ let data = AvatarData::try_from(payload).unwrap();
+ save_avatar(from, id.0, &data.data).unwrap();
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+}
+
+fn save_avatar(from: &Jid, id: String, data: &[u8]) -> io::Result<()> {
+ let directory = format!("data/{}", from);
+ let filename = format!("data/{}/{}", from, id);
+ println!(
+ "Saving avatar from [1m{}[0m to [4m{}[0m.",
+ from, filename
+ );
+ create_dir_all(directory)?;
+ let mut file = File::create(filename)?;
+ file.write_all(data)
+}
@@ -0,0 +1,106 @@
+use futures::{future, Future, Sink, Stream};
+use std::convert::TryFrom;
+use std::env::args;
+use std::process::exit;
+use tokio::runtime::current_thread::Runtime;
+use tokio_xmpp::{Client, Packet};
+use xmpp_parsers::{Jid, Element};
+use xmpp_parsers::message::{Body, Message, MessageType};
+use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
+
+fn main() {
+ let args: Vec<String> = args().collect();
+ if args.len() != 3 {
+ println!("Usage: {} <jid> <password>", args[0]);
+ exit(1);
+ }
+ let jid = &args[1];
+ let password = &args[2];
+
+ // tokio_core context
+ let mut rt = Runtime::new().unwrap();
+ // Client instance
+ let client = Client::new(jid, password).unwrap();
+
+ // Make the two interfaces for sending and receiving independent
+ // of each other so we can move one into a closure.
+ let (sink, stream) = client.split();
+
+ // Create outgoing pipe
+ let (mut tx, rx) = futures::unsync::mpsc::unbounded();
+ rt.spawn(
+ rx.forward(
+ sink.sink_map_err(|_| panic!("Pipe"))
+ )
+ .map(|(rx, mut sink)| {
+ drop(rx);
+ let _ = sink.close();
+ })
+ .map_err(|e| {
+ panic!("Send error: {:?}", e);
+ })
+ );
+
+ // Main loop, processes events
+ let mut wait_for_stream_end = false;
+ let done = stream.for_each(move |event| {
+ if wait_for_stream_end {
+ /* Do nothing */
+ } else if event.is_online() {
+ let jid = event.get_jid()
+ .map(|jid| format!("{}", jid))
+ .unwrap_or("unknown".to_owned());
+ println!("Online at {}", jid);
+
+ let presence = make_presence();
+ tx.start_send(Packet::Stanza(presence)).unwrap();
+ } else if let Some(message) = event
+ .into_stanza()
+ .and_then(|stanza| Message::try_from(stanza).ok())
+ {
+ match (message.from, message.bodies.get("")) {
+ (Some(ref from), Some(ref body)) if body.0 == "die" => {
+ println!("Secret die command triggered by {}", from);
+ wait_for_stream_end = true;
+ tx.start_send(Packet::StreamEnd).unwrap();
+ }
+ (Some(ref from), Some(ref body)) => {
+ if message.type_ != MessageType::Error {
+ // This is a message we'll echo
+ let reply = make_reply(from.clone(), &body.0);
+ tx.start_send(Packet::Stanza(reply)).unwrap();
+ }
+ }
+ _ => {}
+ }
+ }
+
+ future::ok(())
+ });
+
+ // Start polling `done`
+ match rt.block_on(done) {
+ Ok(_) => (),
+ Err(e) => {
+ println!("Fatal: {}", e);
+ ()
+ }
+ }
+}
+
+// Construct a <presence/>
+fn make_presence() -> Element {
+ let mut presence = Presence::new(PresenceType::None);
+ presence.show = Some(PresenceShow::Chat);
+ presence
+ .statuses
+ .insert(String::from("en"), String::from("Echoing messages."));
+ presence.into()
+}
+
+// Construct a chat <message/>
+fn make_reply(to: Jid, body: &str) -> Element {
+ let mut message = Message::new(Some(to));
+ message.bodies.insert(String::new(), Body(body.to_owned()));
+ message.into()
+}
@@ -0,0 +1,99 @@
+use futures::{future, Sink, Stream};
+use std::convert::TryFrom;
+use std::env::args;
+use std::process::exit;
+use std::str::FromStr;
+use tokio::runtime::current_thread::Runtime;
+use tokio_xmpp::Component;
+use xmpp_parsers::{Jid, Element};
+use xmpp_parsers::message::{Body, Message, MessageType};
+use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
+
+fn main() {
+ let args: Vec<String> = args().collect();
+ if args.len() < 3 || args.len() > 5 {
+ println!("Usage: {} <jid> <password> [server] [port]", args[0]);
+ exit(1);
+ }
+ let jid = &args[1];
+ let password = &args[2];
+ let server = &args
+ .get(3)
+ .unwrap()
+ .parse()
+ .unwrap_or("127.0.0.1".to_owned());
+ let port: u16 = args.get(4).unwrap().parse().unwrap_or(5347u16);
+
+ // tokio_core context
+ let mut rt = Runtime::new().unwrap();
+ // Component instance
+ println!("{} {} {} {}", jid, password, server, port);
+ let component = Component::new(jid, password, server, port).unwrap();
+
+ // Make the two interfaces for sending and receiving independent
+ // of each other so we can move one into a closure.
+ println!("Got it: {}", component.jid.clone());
+ let (mut sink, stream) = component.split();
+ // Wrap sink in Option so that we can take() it for the send(self)
+ // to consume and return it back when ready.
+ let mut send = move |stanza| {
+ sink.start_send(stanza).expect("start_send");
+ };
+ // Main loop, processes events
+ let done = stream.for_each(|event| {
+ if event.is_online() {
+ println!("Online!");
+
+ // TODO: replace these hardcoded JIDs
+ let presence = make_presence(
+ Jid::from_str("test@component.linkmauve.fr/coucou").unwrap(),
+ Jid::from_str("linkmauve@linkmauve.fr").unwrap(),
+ );
+ send(presence);
+ } else if let Some(message) = event
+ .into_stanza()
+ .and_then(|stanza| Message::try_from(stanza).ok())
+ {
+ // This is a message we'll echo
+ match (message.from, message.bodies.get("")) {
+ (Some(from), Some(body)) => {
+ if message.type_ != MessageType::Error {
+ let reply = make_reply(from, &body.0);
+ send(reply);
+ }
+ }
+ _ => (),
+ }
+ }
+
+ Box::new(future::ok(()))
+ });
+
+ // Start polling `done`
+ match rt.block_on(done) {
+ Ok(_) => (),
+ Err(e) => {
+ println!("Fatal: {}", e);
+ ()
+ }
+ }
+}
+
+// Construct a <presence/>
+fn make_presence(from: Jid, to: Jid) -> Element {
+ let mut presence = Presence::new(PresenceType::None);
+ presence.from = Some(from);
+ presence.to = Some(to);
+ presence.show = Some(PresenceShow::Chat);
+ presence
+ .statuses
+ .insert(String::from("en"), String::from("Echoing messages."));
+ presence.into()
+}
+
+// Construct a chat <message/>
+fn make_reply(to: Jid, body: &str) -> Element {
+ let mut message = Message::new(Some(to));
+ message.bodies.insert(String::new(), Body(body.to_owned()));
+ message.into()
+}
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="Layer_1"
+ xml:space="preserve"
+ height="1070.54"
+ viewBox="0 0 1251.4018 1070.5223"
+ width="1251.4302"
+ version="1.1"
+ y="0px"
+ x="0px"
+ enable-background="new 0 0 176.486 181.437"
+ sodipodi:docname="logo.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ inkscape:export-filename="/tmp/tokio-xmpp.png"
+ inkscape:export-xdpi="63.689999"
+ inkscape:export-ydpi="63.689999"><metadata
+ id="metadata41"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs39"><radialGradient
+ inkscape:collect="always"
+ xlink:href="#SVGID_1_"
+ id="radialGradient4654"
+ cx="166.88985"
+ cy="40.555923"
+ fx="166.88985"
+ fy="40.555923"
+ r="498.49179"
+ gradientTransform="matrix(1.2093833,0,0,1.0737613,423.8704,491.7138)"
+ gradientUnits="userSpaceOnUse" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#SVGID_1_"
+ id="linearGradient4656"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(1885.557,-704.53708)"
+ x1="-1807.2"
+ y1="125.86"
+ x2="-1807.2"
+ y2="0.00048828" /></defs><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1446"
+ inkscape:window-height="1056"
+ id="namedview37"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="0.32517636"
+ inkscape:cx="488.51154"
+ inkscape:cy="460.25958"
+ inkscape:window-x="0"
+ inkscape:window-y="20"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="Layer_1" />
+<linearGradient
+ id="SVGID_1_"
+ y2="0.00048828"
+ gradientUnits="userSpaceOnUse"
+ x2="-1807.2"
+ gradientTransform="translate(1885.557,-704.53708)"
+ y1="125.86"
+ x1="-1807.2">
+ <stop
+ stop-color="#1b3967"
+ offset=".011"
+ id="stop2" />
+ <stop
+ stop-color="#13b5ea"
+ offset=".467"
+ id="stop4" />
+ <stop
+ stop-color="#002b5c"
+ offset=".9945"
+ id="stop6" />
+</linearGradient>
+
+
+<linearGradient
+ id="SVGID_2_"
+ y2="1.279e-13"
+ gradientUnits="userSpaceOnUse"
+ x2="-1073.2"
+ gradientTransform="matrix(-1,0,0,1,-1038.643,-704.53708)"
+ y1="126.85"
+ x1="-1073.2">
+ <stop
+ stop-color="#1b3967"
+ offset=".011"
+ id="stop13" />
+ <stop
+ stop-color="#13b5ea"
+ offset=".467"
+ id="stop15" />
+ <stop
+ stop-color="#002b5c"
+ offset=".9945"
+ id="stop17" />
+</linearGradient>
+
+
+
+
+
+
+
+
+<path
+ style="fill:url(#radialGradient4654);fill-opacity:1;stroke-width:1.20936334"
@@ -0,0 +1,116 @@
+use std::str::FromStr;
+use std::collections::HashSet;
+use std::convert::TryFrom;
+use futures::{Future, Poll, Stream, future::{ok, err, IntoFuture}};
+use sasl::client::mechanisms::{Anonymous, Plain, Scram};
+use sasl::client::Mechanism;
+use sasl::common::scram::{Sha1, Sha256};
+use sasl::common::Credentials;
+use tokio_io::{AsyncRead, AsyncWrite};
+use xmpp_parsers::sasl::{Auth, Challenge, Failure, Mechanism as XMPPMechanism, Response, Success};
+
+use crate::xmpp_codec::Packet;
+use crate::xmpp_stream::XMPPStream;
+use crate::{AuthError, Error, ProtocolError};
+
+const NS_XMPP_SASL: &str = "urn:ietf:params:xml:ns:xmpp-sasl";
+
+pub struct ClientAuth<S: AsyncRead + AsyncWrite> {
+ future: Box<dyn Future<Item = XMPPStream<S>, Error = Error>>,
+}
+
+impl<S: AsyncRead + AsyncWrite + 'static> ClientAuth<S> {
+ pub fn new(stream: XMPPStream<S>, creds: Credentials) -> Result<Self, Error> {
+ let local_mechs: Vec<Box<dyn Fn() -> Box<dyn Mechanism>>> = vec![
+ Box::new(|| Box::new(Scram::<Sha256>::from_credentials(creds.clone()).unwrap())),
+ Box::new(|| Box::new(Scram::<Sha1>::from_credentials(creds.clone()).unwrap())),
+ Box::new(|| Box::new(Plain::from_credentials(creds.clone()).unwrap())),
+ Box::new(|| Box::new(Anonymous::new())),
+ ];
+
+ let remote_mechs: HashSet<String> = stream
+ .stream_features
+ .get_child("mechanisms", NS_XMPP_SASL)
+ .ok_or(AuthError::NoMechanism)?
+ .children()
+ .filter(|child| child.is("mechanism", NS_XMPP_SASL))
+ .map(|mech_el| mech_el.text())
+ .collect();
+
+ for local_mech in local_mechs {
+ let mut mechanism = local_mech();
+ if remote_mechs.contains(mechanism.name()) {
+ let initial = mechanism.initial().map_err(AuthError::Sasl)?;
+ let mechanism_name = XMPPMechanism::from_str(mechanism.name()).map_err(ProtocolError::Parsers)?;
+
+ let send_initial = Box::new(stream.send_stanza(Auth {
+ mechanism: mechanism_name,
+ data: initial,
+ }))
+ .map_err(Error::Io);
+ let future = Box::new(send_initial.and_then(
+ |stream| Self::handle_challenge(stream, mechanism)
+ ).and_then(
+ |stream| stream.restart()
+ ));
+ return Ok(ClientAuth {
+ future,
+ });
+ }
+ }
+
+ Err(AuthError::NoMechanism)?
+ }
+
+ fn handle_challenge(stream: XMPPStream<S>, mut mechanism: Box<dyn Mechanism>) -> Box<dyn Future<Item = XMPPStream<S>, Error = Error>> {
+ Box::new(
+ stream.into_future()
+ .map_err(|(e, _stream)| e.into())
+ .and_then(|(stanza, stream)| {
+ match stanza {
+ Some(Packet::Stanza(stanza)) => {
+ if let Ok(challenge) = Challenge::try_from(stanza.clone()) {
+ let response = mechanism
+ .response(&challenge.data);
+ Box::new(
+ response
+ .map_err(|e| AuthError::Sasl(e).into())
+ .into_future()
+ .and_then(|response| {
+ // Send response and loop
+ stream.send_stanza(Response { data: response })
+ .map_err(Error::Io)
+ .and_then(|stream| Self::handle_challenge(stream, mechanism))
+ })
+ )
+ } else if let Ok(_) = Success::try_from(stanza.clone()) {
+ Box::new(ok(stream))
+ } else if let Ok(failure) = Failure::try_from(stanza.clone()) {
+ Box::new(err(Error::Auth(AuthError::Fail(failure.defined_condition))))
+ } else if stanza.name() == "failure" {
+ // Workaround for https://gitlab.com/xmpp-rs/xmpp-parsers/merge_requests/1
+ Box::new(err(Error::Auth(AuthError::Sasl("failure".to_string()))))
+ } else {
+ // ignore and loop
+ Self::handle_challenge(stream, mechanism)
+ }
+ }
+ Some(_) => {
+ // ignore and loop
+ Self::handle_challenge(stream, mechanism)
+ }
+ None => Box::new(err(Error::Disconnected))
+ }
+ })
+ )
+ }
+}
+
+impl<S: AsyncRead + AsyncWrite> Future for ClientAuth<S> {
+ type Item = XMPPStream<S>;
+ type Error = Error;
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ self.future.poll()
+ }
+}
@@ -0,0 +1,102 @@
+use futures::{sink, Async, Future, Poll, Stream};
+use std::convert::TryFrom;
+use std::mem::replace;
+use tokio_io::{AsyncRead, AsyncWrite};
+use xmpp_parsers::Jid;
+use xmpp_parsers::bind::{BindQuery, BindResponse};
+use xmpp_parsers::iq::{Iq, IqType};
+
+use crate::xmpp_codec::Packet;
+use crate::xmpp_stream::XMPPStream;
+use crate::{Error, ProtocolError};
+
+const NS_XMPP_BIND: &str = "urn:ietf:params:xml:ns:xmpp-bind";
+const BIND_REQ_ID: &str = "resource-bind";
+
+pub enum ClientBind<S: AsyncWrite> {
+ Unsupported(XMPPStream<S>),
+ WaitSend(sink::Send<XMPPStream<S>>),
+ WaitRecv(XMPPStream<S>),
+ Invalid,
+}
+
+impl<S: AsyncWrite> ClientBind<S> {
+ /// Consumes and returns the stream to express that you cannot use
+ /// the stream for anything else until the resource binding
+ /// req/resp are done.
+ pub fn new(stream: XMPPStream<S>) -> Self {
+ match stream.stream_features.get_child("bind", NS_XMPP_BIND) {
+ None =>
+ // No resource binding available,
+ // return the (probably // usable) stream immediately
+ {
+ ClientBind::Unsupported(stream)
+ }
+ Some(_) => {
+ let resource;
+ if let Jid::Full(jid) = stream.jid.clone() {
+ resource = Some(jid.resource);
+ } else {
+ resource = None;
+ }
+ let iq = Iq::from_set(BIND_REQ_ID, BindQuery::new(resource));
+ let send = stream.send_stanza(iq);
+ ClientBind::WaitSend(send)
+ }
+ }
+ }
+}
+
+impl<S: AsyncRead + AsyncWrite> Future for ClientBind<S> {
+ type Item = XMPPStream<S>;
+ type Error = Error;
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ let state = replace(self, ClientBind::Invalid);
+
+ match state {
+ ClientBind::Unsupported(stream) => Ok(Async::Ready(stream)),
+ ClientBind::WaitSend(mut send) => match send.poll() {
+ Ok(Async::Ready(stream)) => {
+ replace(self, ClientBind::WaitRecv(stream));
+ self.poll()
+ }
+ Ok(Async::NotReady) => {
+ replace(self, ClientBind::WaitSend(send));
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e)?,
+ },
+ ClientBind::WaitRecv(mut stream) => match stream.poll() {
+ Ok(Async::Ready(Some(Packet::Stanza(stanza)))) => match Iq::try_from(stanza) {
+ Ok(iq) => {
+ if iq.id == BIND_REQ_ID {
+ match iq.payload {
+ IqType::Result(payload) => {
+ payload
+ .and_then(|payload| BindResponse::try_from(payload).ok())
+ .map(|bind| stream.jid = bind.into());
+ Ok(Async::Ready(stream))
+ }
+ _ => Err(ProtocolError::InvalidBindResponse)?,
+ }
+ } else {
+ Ok(Async::NotReady)
+ }
+ }
+ _ => Ok(Async::NotReady),
+ },
+ Ok(Async::Ready(_)) => {
+ replace(self, ClientBind::WaitRecv(stream));
+ self.poll()
+ }
+ Ok(Async::NotReady) => {
+ replace(self, ClientBind::WaitRecv(stream));
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e)?,
+ },
+ ClientBind::Invalid => unreachable!(),
+ }
+ }
+}
@@ -0,0 +1,236 @@
+use futures::{done, Async, AsyncSink, Future, Poll, Sink, StartSend, Stream};
+use idna;
+use xmpp_parsers::{Jid, JidParseError};
+use sasl::common::{ChannelBinding, Credentials};
+use std::mem::replace;
+use std::str::FromStr;
+use tokio::net::TcpStream;
+use tokio_io::{AsyncRead, AsyncWrite};
+use tokio_tls::TlsStream;
+
+use super::event::Event;
+use super::happy_eyeballs::Connecter;
+use super::starttls::{StartTlsClient, NS_XMPP_TLS};
+use super::xmpp_codec::Packet;
+use super::xmpp_stream;
+use super::{Error, ProtocolError};
+
+mod auth;
+use self::auth::ClientAuth;
+mod bind;
+use self::bind::ClientBind;
+
+/// XMPP client connection and state
+pub struct Client {
+ state: ClientState,
+}
+
+type XMPPStream = xmpp_stream::XMPPStream<TlsStream<TcpStream>>;
+const NS_JABBER_CLIENT: &str = "jabber:client";
+
+enum ClientState {
+ Invalid,
+ Disconnected,
+ Connecting(Box<dyn Future<Item = XMPPStream, Error = Error>>),
+ Connected(XMPPStream),
+}
+
+impl Client {
+ /// Start a new XMPP client
+ ///
+ /// Start polling the returned instance so that it will connect
+ /// and yield events.
+ pub fn new(jid: &str, password: &str) -> Result<Self, JidParseError> {
+ let jid = Jid::from_str(jid)?;
+ let client = Self::new_with_jid(jid, password);
+ Ok(client)
+ }
+
+ /// Start a new client given that the JID is already parsed.
+ pub fn new_with_jid(jid: Jid, password: &str) -> Self {
+ let password = password.to_owned();
+ let connect = Self::make_connect(jid, password.clone());
+ let client = Client {
+ state: ClientState::Connecting(Box::new(connect)),
+ };
+ client
+ }
+
+ fn make_connect(jid: Jid, password: String) -> impl Future<Item = XMPPStream, Error = Error> {
+ let username = jid.clone().node().unwrap();
+ let jid1 = jid.clone();
+ let jid2 = jid.clone();
+ let password = password;
+ done(idna::domain_to_ascii(&jid.domain()))
+ .map_err(|_| Error::Idna)
+ .and_then(|domain| {
+ done(Connecter::from_lookup(
+ &domain,
+ Some("_xmpp-client._tcp"),
+ 5222,
+ ))
+ })
+ .flatten()
+ .and_then(move |tcp_stream| {
+ xmpp_stream::XMPPStream::start(tcp_stream, jid1, NS_JABBER_CLIENT.to_owned())
+ })
+ .and_then(|xmpp_stream| {
+ if Self::can_starttls(&xmpp_stream) {
+ Ok(Self::starttls(xmpp_stream))
+ } else {
+ Err(Error::Protocol(ProtocolError::NoTls))
+ }
+ })
+ .flatten()
+ .and_then(|tls_stream| XMPPStream::start(tls_stream, jid2, NS_JABBER_CLIENT.to_owned()))
+ .and_then(
+ move |xmpp_stream| done(Self::auth(xmpp_stream, username, password)), // TODO: flatten?
+ )
+ .and_then(|auth| auth)
+ .and_then(|xmpp_stream| Self::bind(xmpp_stream))
+ .and_then(|xmpp_stream| {
+ // println!("Bound to {}", xmpp_stream.jid);
+ Ok(xmpp_stream)
+ })
+ }
+
+ fn can_starttls<S>(stream: &xmpp_stream::XMPPStream<S>) -> bool {
+ stream
+ .stream_features
+ .get_child("starttls", NS_XMPP_TLS)
+ .is_some()
+ }
+
+ fn starttls<S: AsyncRead + AsyncWrite>(
+ stream: xmpp_stream::XMPPStream<S>,
+ ) -> StartTlsClient<S> {
+ StartTlsClient::from_stream(stream)
+ }
+
+ fn auth<S: AsyncRead + AsyncWrite + 'static>(
+ stream: xmpp_stream::XMPPStream<S>,
+ username: String,
+ password: String,
+ ) -> Result<ClientAuth<S>, Error> {
+ let creds = Credentials::default()
+ .with_username(username)
+ .with_password(password)
+ .with_channel_binding(ChannelBinding::None);
+ ClientAuth::new(stream, creds)
+ }
+
+ fn bind<S: AsyncWrite>(stream: xmpp_stream::XMPPStream<S>) -> ClientBind<S> {
+ ClientBind::new(stream)
+ }
+
+ /// Get the client's bound JID (the one reported by the XMPP
+ /// server).
+ pub fn bound_jid(&self) -> Option<&Jid> {
+ match self.state {
+ ClientState::Connected(ref stream) => Some(&stream.jid),
+ _ => None,
+ }
+ }
+}
+
+impl Stream for Client {
+ type Item = Event;
+ type Error = Error;
+
+ fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
+ let state = replace(&mut self.state, ClientState::Invalid);
+
+ match state {
+ ClientState::Invalid => Err(Error::InvalidState),
+ ClientState::Disconnected => Ok(Async::Ready(None)),
+ ClientState::Connecting(mut connect) => match connect.poll() {
+ Ok(Async::Ready(stream)) => {
+ let jid = stream.jid.clone();
+ self.state = ClientState::Connected(stream);
+ Ok(Async::Ready(Some(Event::Online(jid))))
+ }
+ Ok(Async::NotReady) => {
+ self.state = ClientState::Connecting(connect);
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e),
+ },
+ ClientState::Connected(mut stream) => {
+ // Poll sink
+ match stream.poll_complete() {
+ Ok(Async::NotReady) => (),
+ Ok(Async::Ready(())) => (),
+ Err(e) => return Err(e)?,
+ };
+
+ // Poll stream
+ match stream.poll() {
+ Ok(Async::Ready(None)) => {
+ // EOF
+ self.state = ClientState::Disconnected;
+ Ok(Async::Ready(Some(Event::Disconnected)))
+ }
+ Ok(Async::Ready(Some(Packet::Stanza(stanza)))) => {
+ // Receive stanza
+ self.state = ClientState::Connected(stream);
+ Ok(Async::Ready(Some(Event::Stanza(stanza))))
+ }
+ Ok(Async::Ready(Some(Packet::Text(_)))) => {
+ // Ignore text between stanzas
+ Ok(Async::NotReady)
+ }
+ Ok(Async::Ready(Some(Packet::StreamStart(_)))) => {
+ // <stream:stream>
+ Err(ProtocolError::InvalidStreamStart.into())
+ }
+ Ok(Async::Ready(Some(Packet::StreamEnd))) => {
+ // End of stream: </stream:stream>
+ Ok(Async::Ready(None))
+ }
+ Ok(Async::NotReady) => {
+ // Try again later
+ self.state = ClientState::Connected(stream);
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e)?,
+ }
+ }
+ }
+ }
+}
+
+impl Sink for Client {
+ type SinkItem = Packet;
+ type SinkError = Error;
+
+ fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
+ match self.state {
+ ClientState::Connected(ref mut stream) =>
+ Ok(stream.start_send(item)?),
+ _ =>
+ Ok(AsyncSink::NotReady(item)),
+ }
+ }
+
+ fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
+ match self.state {
+ ClientState::Connected(ref mut stream) => stream.poll_complete().map_err(|e| e.into()),
+ _ => Ok(Async::Ready(())),
+ }
+ }
+
+ /// This closes the inner TCP stream.
+ ///
+ /// To synchronize your shutdown with the server side, you should
+ /// first send `Packet::StreamEnd` and wait for the end of the
+ /// incoming stream before closing the connection.
+ fn close(&mut self) -> Poll<(), Self::SinkError> {
+ match self.state {
+ ClientState::Connected(ref mut stream) =>
+ stream.close()
+ .map_err(|e| e.into()),
+ _ =>
+ Ok(Async::Ready(())),
+ }
+ }
+}
@@ -0,0 +1,89 @@
+use futures::{sink, Async, Future, Poll, Stream};
+use std::mem::replace;
+use tokio_io::{AsyncRead, AsyncWrite};
+use xmpp_parsers::component::Handshake;
+
+use crate::xmpp_codec::Packet;
+use crate::xmpp_stream::XMPPStream;
+use crate::{AuthError, Error};
+
+const NS_JABBER_COMPONENT_ACCEPT: &str = "jabber:component:accept";
+
+pub struct ComponentAuth<S: AsyncWrite> {
+ state: ComponentAuthState<S>,
+}
+
+enum ComponentAuthState<S: AsyncWrite> {
+ WaitSend(sink::Send<XMPPStream<S>>),
+ WaitRecv(XMPPStream<S>),
+ Invalid,
+}
+
+impl<S: AsyncWrite> ComponentAuth<S> {
+ // TODO: doesn't have to be a Result<> actually
+ pub fn new(stream: XMPPStream<S>, password: String) -> Result<Self, Error> {
+ // FIXME: huge hack, shouldnβt be an element!
+ let sid = stream.stream_features.name().to_owned();
+ let mut this = ComponentAuth {
+ state: ComponentAuthState::Invalid,
+ };
+ this.send(
+ stream,
+ Handshake::from_password_and_stream_id(&password, &sid),
+ );
+ Ok(this)
+ }
+
+ fn send(&mut self, stream: XMPPStream<S>, handshake: Handshake) {
+ let nonza = handshake;
+ let send = stream.send_stanza(nonza);
+
+ self.state = ComponentAuthState::WaitSend(send);
+ }
+}
+
+impl<S: AsyncRead + AsyncWrite> Future for ComponentAuth<S> {
+ type Item = XMPPStream<S>;
+ type Error = Error;
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ let state = replace(&mut self.state, ComponentAuthState::Invalid);
+
+ match state {
+ ComponentAuthState::WaitSend(mut send) => match send.poll() {
+ Ok(Async::Ready(stream)) => {
+ self.state = ComponentAuthState::WaitRecv(stream);
+ self.poll()
+ }
+ Ok(Async::NotReady) => {
+ self.state = ComponentAuthState::WaitSend(send);
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e)?,
+ },
+ ComponentAuthState::WaitRecv(mut stream) => match stream.poll() {
+ Ok(Async::Ready(Some(Packet::Stanza(ref stanza))))
+ if stanza.is("handshake", NS_JABBER_COMPONENT_ACCEPT) =>
+ {
+ self.state = ComponentAuthState::Invalid;
+ Ok(Async::Ready(stream))
+ }
+ Ok(Async::Ready(Some(Packet::Stanza(ref stanza))))
+ if stanza.is("error", "http://etherx.jabber.org/streams") =>
+ {
+ Err(AuthError::ComponentFail.into())
+ }
+ Ok(Async::Ready(_event)) => {
+ // println!("ComponentAuth ignore {:?}", _event);
+ Ok(Async::NotReady)
+ }
+ Ok(_) => {
+ self.state = ComponentAuthState::WaitRecv(stream);
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e)?,
+ },
+ ComponentAuthState::Invalid => unreachable!(),
+ }
+ }
+}
@@ -0,0 +1,163 @@
+//! Components in XMPP are services/gateways that are logged into an
+//! XMPP server under a JID consisting of just a domain name. They are
+//! allowed to use any user and resource identifiers in their stanzas.
+use futures::{done, Async, AsyncSink, Future, Poll, Sink, StartSend, Stream};
+use xmpp_parsers::{Jid, JidParseError, Element};
+use std::mem::replace;
+use std::str::FromStr;
+use tokio::net::TcpStream;
+use tokio_io::{AsyncRead, AsyncWrite};
+
+use super::event::Event;
+use super::happy_eyeballs::Connecter;
+use super::xmpp_codec::Packet;
+use super::xmpp_stream;
+use super::Error;
+
+mod auth;
+use self::auth::ComponentAuth;
+
+/// Component connection to an XMPP server
+pub struct Component {
+ /// The component's Jabber-Id
+ pub jid: Jid,
+ state: ComponentState,
+}
+
+type XMPPStream = xmpp_stream::XMPPStream<TcpStream>;
+const NS_JABBER_COMPONENT_ACCEPT: &str = "jabber:component:accept";
+
+enum ComponentState {
+ Invalid,
+ Disconnected,
+ Connecting(Box<dyn Future<Item = XMPPStream, Error = Error>>),
+ Connected(XMPPStream),
+}
+
+impl Component {
+ /// Start a new XMPP component
+ ///
+ /// Start polling the returned instance so that it will connect
+ /// and yield events.
+ pub fn new(jid: &str, password: &str, server: &str, port: u16) -> Result<Self, JidParseError> {
+ let jid = Jid::from_str(jid)?;
+ let password = password.to_owned();
+ let connect = Self::make_connect(jid.clone(), password, server, port);
+ Ok(Component {
+ jid,
+ state: ComponentState::Connecting(Box::new(connect)),
+ })
+ }
+
+ fn make_connect(
+ jid: Jid,
+ password: String,
+ server: &str,
+ port: u16,
+ ) -> impl Future<Item = XMPPStream, Error = Error> {
+ let jid1 = jid.clone();
+ let password = password;
+ done(Connecter::from_lookup(server, None, port))
+ .flatten()
+ .and_then(move |tcp_stream| {
+ xmpp_stream::XMPPStream::start(
+ tcp_stream,
+ jid1,
+ NS_JABBER_COMPONENT_ACCEPT.to_owned(),
+ )
+ })
+ .and_then(move |xmpp_stream| Self::auth(xmpp_stream, password).expect("auth"))
+ }
+
+ fn auth<S: AsyncRead + AsyncWrite>(
+ stream: xmpp_stream::XMPPStream<S>,
+ password: String,
+ ) -> Result<ComponentAuth<S>, Error> {
+ ComponentAuth::new(stream, password)
+ }
+}
+
+impl Stream for Component {
+ type Item = Event;
+ type Error = Error;
+
+ fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
+ let state = replace(&mut self.state, ComponentState::Invalid);
+
+ match state {
+ ComponentState::Invalid => Err(Error::InvalidState),
+ ComponentState::Disconnected => Ok(Async::Ready(None)),
+ ComponentState::Connecting(mut connect) => match connect.poll() {
+ Ok(Async::Ready(stream)) => {
+ self.state = ComponentState::Connected(stream);
+ Ok(Async::Ready(Some(Event::Online(self.jid.clone()))))
+ }
+ Ok(Async::NotReady) => {
+ self.state = ComponentState::Connecting(connect);
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e),
+ },
+ ComponentState::Connected(mut stream) => {
+ // Poll sink
+ match stream.poll_complete() {
+ Ok(Async::NotReady) => (),
+ Ok(Async::Ready(())) => (),
+ Err(e) => return Err(e)?,
+ };
+
+ // Poll stream
+ match stream.poll() {
+ Ok(Async::NotReady) => {
+ self.state = ComponentState::Connected(stream);
+ Ok(Async::NotReady)
+ }
+ Ok(Async::Ready(None)) => {
+ // EOF
+ self.state = ComponentState::Disconnected;
+ Ok(Async::Ready(Some(Event::Disconnected)))
+ }
+ Ok(Async::Ready(Some(Packet::Stanza(stanza)))) => {
+ self.state = ComponentState::Connected(stream);
+ Ok(Async::Ready(Some(Event::Stanza(stanza))))
+ }
+ Ok(Async::Ready(_)) => {
+ self.state = ComponentState::Connected(stream);
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e)?,
+ }
+ }
+ }
+ }
+}
+
+impl Sink for Component {
+ type SinkItem = Element;
+ type SinkError = Error;
+
+ fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
+ match self.state {
+ ComponentState::Connected(ref mut stream) => match stream
+ .start_send(Packet::Stanza(item))
+ {
+ Ok(AsyncSink::NotReady(Packet::Stanza(stanza))) => Ok(AsyncSink::NotReady(stanza)),
+ Ok(AsyncSink::NotReady(_)) => {
+ panic!("Component.start_send with stanza but got something else back")
+ }
+ Ok(AsyncSink::Ready) => Ok(AsyncSink::Ready),
+ Err(e) => Err(e)?,
+ },
+ _ => Ok(AsyncSink::NotReady(item)),
+ }
+ }
+
+ fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
+ match &mut self.state {
+ &mut ComponentState::Connected(ref mut stream) => {
+ stream.poll_complete().map_err(|e| e.into())
+ }
+ _ => Ok(Async::Ready(())),
+ }
+ }
+}
@@ -0,0 +1,224 @@
+use native_tls::Error as TlsError;
+use std::borrow::Cow;
+use std::error::Error as StdError;
+use std::fmt;
+use std::io::Error as IoError;
+use std::str::Utf8Error;
+use trust_dns_proto::error::ProtoError;
+use trust_dns_resolver::error::ResolveError;
+
+use xmpp_parsers::Error as ParsersError;
+use xmpp_parsers::sasl::DefinedCondition as SaslDefinedCondition;
+
+/// Top-level error type
+#[derive(Debug)]
+pub enum Error {
+ /// I/O error
+ Io(IoError),
+ /// Error resolving DNS and establishing a connection
+ Connection(ConnecterError),
+ /// DNS label conversion error, no details available from module
+ /// `idna`
+ Idna,
+ /// Protocol-level error
+ Protocol(ProtocolError),
+ /// Authentication error
+ Auth(AuthError),
+ /// TLS error
+ Tls(TlsError),
+ /// Connection closed
+ Disconnected,
+ /// Shoud never happen
+ InvalidState,
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Error::Io(e) => write!(fmt, "IO error: {}", e),
+ Error::Connection(e) => write!(fmt, "connection error: {}", e),
+ Error::Idna => write!(fmt, "IDNA error"),
+ Error::Protocol(e) => write!(fmt, "protocol error: {}", e),
+ Error::Auth(e) => write!(fmt, "authentication error: {}", e),
+ Error::Tls(e) => write!(fmt, "TLS error: {}", e),
+ Error::Disconnected => write!(fmt, "disconnected"),
+ Error::InvalidState => write!(fmt, "invalid state"),
+ }
+ }
+}
+
+impl From<IoError> for Error {
+ fn from(e: IoError) -> Self {
+ Error::Io(e)
+ }
+}
+
+impl From<ConnecterError> for Error {
+ fn from(e: ConnecterError) -> Self {
+ Error::Connection(e)
+ }
+}
+
+impl From<ProtocolError> for Error {
+ fn from(e: ProtocolError) -> Self {
+ Error::Protocol(e)
+ }
+}
+
+impl From<AuthError> for Error {
+ fn from(e: AuthError) -> Self {
+ Error::Auth(e)
+ }
+}
+
+impl From<TlsError> for Error {
+ fn from(e: TlsError) -> Self {
+ Error::Tls(e)
+ }
+}
+
+/// Causes for stream parsing errors
+#[derive(Debug)]
+pub enum ParserError {
+ /// Encoding error
+ Utf8(Utf8Error),
+ /// XML parse error
+ Parse(ParseError),
+ /// Illegal `</>`
+ ShortTag,
+ /// Required by `impl Decoder`
+ Io(IoError),
+}
+
+impl fmt::Display for ParserError {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ ParserError::Utf8(e) => write!(fmt, "UTF-8 error: {}", e),
+ ParserError::Parse(e) => write!(fmt, "parse error: {}", e),
+ ParserError::ShortTag => write!(fmt, "short tag"),
+ ParserError::Io(e) => write!(fmt, "IO error: {}", e),
+ }
+ }
+}
+
+impl From<IoError> for ParserError {
+ fn from(e: IoError) -> Self {
+ ParserError::Io(e)
+ }
+}
+
+impl From<ParserError> for Error {
+ fn from(e: ParserError) -> Self {
+ ProtocolError::Parser(e).into()
+ }
+}
+
+/// XML parse error wrapper type
+#[derive(Debug)]
+pub struct ParseError(pub Cow<'static, str>);
+
+impl StdError for ParseError {
+ fn description(&self) -> &str {
+ self.0.as_ref()
+ }
+ fn cause(&self) -> Option<&dyn StdError> {
+ None
+ }
+}
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+/// XMPP protocol-level error
+#[derive(Debug)]
+pub enum ProtocolError {
+ /// XML parser error
+ Parser(ParserError),
+ /// Error with expected stanza schema
+ Parsers(ParsersError),
+ /// No TLS available
+ NoTls,
+ /// Invalid response to resource binding
+ InvalidBindResponse,
+ /// No xmlns attribute in <stream:stream>
+ NoStreamNamespace,
+ /// No id attribute in <stream:stream>
+ NoStreamId,
+ /// Encountered an unexpected XML token
+ InvalidToken,
+ /// Unexpected <stream:stream> (shouldn't occur)
+ InvalidStreamStart,
+}
+
+impl fmt::Display for ProtocolError {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ ProtocolError::Parser(e) => write!(fmt, "XML parser error: {}", e),
+ ProtocolError::Parsers(e) => write!(fmt, "error with expected stanza schema: {}", e),
+ ProtocolError::NoTls => write!(fmt, "no TLS available"),
+ ProtocolError::InvalidBindResponse => write!(fmt, "invalid response to resource binding"),
+ ProtocolError::NoStreamNamespace => write!(fmt, "no xmlns attribute in <stream:stream>"),
+ ProtocolError::NoStreamId => write!(fmt, "no id attribute in <stream:stream>"),
+ ProtocolError::InvalidToken => write!(fmt, "encountered an unexpected XML token"),
+ ProtocolError::InvalidStreamStart => write!(fmt, "unexpected <stream:stream>"),
+ }
+ }
+}
+
+impl From<ParserError> for ProtocolError {
+ fn from(e: ParserError) -> Self {
+ ProtocolError::Parser(e)
+ }
+}
+
+impl From<ParsersError> for ProtocolError {
+ fn from(e: ParsersError) -> Self {
+ ProtocolError::Parsers(e)
+ }
+}
+
+/// Authentication error
+#[derive(Debug)]
+pub enum AuthError {
+ /// No matching SASL mechanism available
+ NoMechanism,
+ /// Local SASL implementation error
+ Sasl(String),
+ /// Failure from server
+ Fail(SaslDefinedCondition),
+ /// Component authentication failure
+ ComponentFail,
+}
+
+impl fmt::Display for AuthError {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ AuthError::NoMechanism => write!(fmt, "no matching SASL mechanism available"),
+ AuthError::Sasl(s) => write!(fmt, "local SASL implementation error: {}", s),
+ AuthError::Fail(c) => write!(fmt, "failure from the server: {:?}", c),
+ AuthError::ComponentFail => write!(fmt, "component authentication failure"),
+ }
+ }
+}
+
+/// Error establishing connection
+#[derive(Debug)]
+pub enum ConnecterError {
+ /// All attempts failed, no error available
+ AllFailed,
+ /// DNS protocol error
+ Dns(ProtoError),
+ /// DNS resolution error
+ Resolve(ResolveError),
+}
+
+impl std::error::Error for ConnecterError {}
+
+impl std::fmt::Display for ConnecterError {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ write!(fmt, "{:?}", self)
+ }
+}
@@ -0,0 +1,54 @@
+use xmpp_parsers::{Element, Jid};
+
+/// High-level event on the Stream implemented by Client and Component
+#[derive(Debug)]
+pub enum Event {
+ /// Stream is connected and initialized
+ Online(Jid),
+ /// Stream end
+ Disconnected,
+ /// Received stanza/nonza
+ Stanza(Element),
+}
+
+impl Event {
+ /// `Online` event?
+ pub fn is_online(&self) -> bool {
+ match *self {
+ Event::Online(_) => true,
+ _ => false,
+ }
+ }
+
+ /// Get the server-assigned JID for the `Online` event
+ pub fn get_jid(&self) -> Option<&Jid> {
+ match *self {
+ Event::Online(ref jid) => Some(jid),
+ _ => None,
+ }
+ }
+
+ /// `Stanza` event?
+ pub fn is_stanza(&self, name: &str) -> bool {
+ match *self {
+ Event::Stanza(ref stanza) => stanza.name() == name,
+ _ => false,
+ }
+ }
+
+ /// If this is a `Stanza` event, get its data
+ pub fn as_stanza(&self) -> Option<&Element> {
+ match *self {
+ Event::Stanza(ref stanza) => Some(stanza),
+ _ => None,
+ }
+ }
+
+ /// If this is a `Stanza` event, unwrap into its data
+ pub fn into_stanza(self) -> Option<Element> {
+ match self {
+ Event::Stanza(stanza) => Some(stanza),
+ _ => None,
+ }
+ }
+}
@@ -0,0 +1,196 @@
+use crate::{ConnecterError, Error};
+use futures::{Async, Future, Poll};
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::collections::VecDeque;
+use std::io::Error as IoError;
+use std::mem;
+use std::net::SocketAddr;
+use tokio::net::tcp::ConnectFuture;
+use tokio::net::TcpStream;
+use trust_dns_resolver::{AsyncResolver, Name, IntoName, Background, BackgroundLookup};
+use trust_dns_resolver::config::LookupIpStrategy;
+use trust_dns_resolver::lookup::SrvLookupFuture;
+use trust_dns_resolver::lookup_ip::LookupIpFuture;
+
+
+enum State {
+ ResolveSrv(AsyncResolver, BackgroundLookup<SrvLookupFuture>),
+ ResolveTarget(AsyncResolver, Background<LookupIpFuture>, u16),
+ Connecting(Option<AsyncResolver>, Vec<RefCell<ConnectFuture>>),
+ Invalid,
+}
+
+pub struct Connecter {
+ fallback_port: u16,
+ srv_domain: Option<Name>,
+ domain: Name,
+ state: State,
+ targets: VecDeque<(Name, u16)>,
+ error: Option<Error>,
+}
+
+fn resolver() -> Result<AsyncResolver, IoError> {
+ let (config, mut opts) = trust_dns_resolver::system_conf::read_system_conf()?;
+ opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
+ let (resolver, resolver_background) = AsyncResolver::new(config, opts);
+ tokio::runtime::current_thread::spawn(resolver_background);
+ Ok(resolver)
+}
+
+impl Connecter {
+ pub fn from_lookup(
+ domain: &str,
+ srv: Option<&str>,
+ fallback_port: u16,
+ ) -> Result<Connecter, Error> {
+ if let Ok(ip) = domain.parse() {
+ // use specified IP address, not domain name, skip the whole dns part
+ let connect = RefCell::new(TcpStream::connect(&SocketAddr::new(ip, fallback_port)));
+ return Ok(Connecter {
+ fallback_port,
+ srv_domain: None,
+ domain: "nohost".into_name().map_err(ConnecterError::Dns)?,
+ state: State::Connecting(None, vec![connect]),
+ targets: VecDeque::new(),
+ error: None,
+ });
+ }
+
+ let srv_domain = match srv {
+ Some(srv) => Some(
+ format!("{}.{}.", srv, domain)
+ .into_name()
+ .map_err(ConnecterError::Dns)?,
+ ),
+ None => None,
+ };
+
+ let mut self_ = Connecter {
+ fallback_port,
+ srv_domain,
+ domain: domain.into_name().map_err(ConnecterError::Dns)?,
+ state: State::Invalid,
+ targets: VecDeque::new(),
+ error: None,
+ };
+
+ let resolver = resolver()?;
+ // Initialize state
+ match &self_.srv_domain {
+ &Some(ref srv_domain) => {
+ let srv_lookup = resolver.lookup_srv(srv_domain.clone());
+ self_.state = State::ResolveSrv(resolver, srv_lookup);
+ }
+ None => {
+ self_.targets = [(self_.domain.clone(), self_.fallback_port)]
+ .into_iter()
+ .cloned()
+ .collect();
+ self_.state = State::Connecting(Some(resolver), vec![]);
+ }
+ }
+
+ Ok(self_)
+ }
+}
+
+impl Future for Connecter {
+ type Item = TcpStream;
+ type Error = Error;
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ let state = mem::replace(&mut self.state, State::Invalid);
+ match state {
+ State::ResolveSrv(resolver, mut srv_lookup) => {
+ match srv_lookup.poll() {
+ Ok(Async::NotReady) => {
+ self.state = State::ResolveSrv(resolver, srv_lookup);
+ Ok(Async::NotReady)
+ }
+ Ok(Async::Ready(srv_result)) => {
+ let srv_map: BTreeMap<_, _> = srv_result
+ .iter()
+ .map(|srv| (srv.priority(), (srv.target().clone(), srv.port())))
+ .collect();
+ let targets = srv_map.into_iter().map(|(_, tp)| tp).collect();
+ self.targets = targets;
+ self.state = State::Connecting(Some(resolver), vec![]);
+ self.poll()
+ }
+ Err(_) => {
+ // ignore, fallback
+ self.targets = [(self.domain.clone(), self.fallback_port)]
+ .into_iter()
+ .cloned()
+ .collect();
+ self.state = State::Connecting(Some(resolver), vec![]);
+ self.poll()
+ }
+ }
+ }
+ State::Connecting(resolver, mut connects) => {
+ if resolver.is_some() && connects.len() == 0 && self.targets.len() > 0 {
+ let resolver = resolver.unwrap();
+ let (host, port) = self.targets.pop_front().unwrap();
+ let ip_lookup = resolver.lookup_ip(host);
+ self.state = State::ResolveTarget(resolver, ip_lookup, port);
+ self.poll()
+ } else if connects.len() > 0 {
+ let mut success = None;
+ connects.retain(|connect| match connect.borrow_mut().poll() {
+ Ok(Async::NotReady) => true,
+ Ok(Async::Ready(connection)) => {
+ success = Some(connection);
+ false
+ }
+ Err(e) => {
+ if self.error.is_none() {
+ self.error = Some(e.into());
+ }
+ false
+ }
+ });
+ match success {
+ Some(connection) => Ok(Async::Ready(connection)),
+ None => {
+ self.state = State::Connecting(resolver, connects);
+ Ok(Async::NotReady)
+ }
+ }
+ } else {
+ // All targets tried
+ match self.error.take() {
+ None => Err(ConnecterError::AllFailed.into()),
+ Some(e) => Err(e),
+ }
+ }
+ }
+ State::ResolveTarget(resolver, mut ip_lookup, port) => {
+ match ip_lookup.poll() {
+ Ok(Async::NotReady) => {
+ self.state = State::ResolveTarget(resolver, ip_lookup, port);
+ Ok(Async::NotReady)
+ }
+ Ok(Async::Ready(ip_result)) => {
+ let connects = ip_result
+ .iter()
+ .map(|ip| RefCell::new(TcpStream::connect(&SocketAddr::new(ip, port))))
+ .collect();
+ self.state = State::Connecting(Some(resolver), connects);
+ self.poll()
+ }
+ Err(e) => {
+ if self.error.is_none() {
+ self.error = Some(ConnecterError::Resolve(e).into());
+ }
+ // ignore, nextβ¦
+ self.state = State::Connecting(Some(resolver), vec![]);
+ self.poll()
+ }
+ }
+ }
+ _ => panic!(""),
+ }
+ }
+}
@@ -0,0 +1,19 @@
+#![deny(unsafe_code, unused, missing_docs, bare_trait_objects)]
+
+//! XMPP implementation with asynchronous I/O using Tokio.
+
+mod starttls;
+mod stream_start;
+pub mod xmpp_codec;
+pub use crate::xmpp_codec::Packet;
+pub mod xmpp_stream;
+pub use crate::starttls::StartTlsClient;
+mod event;
+mod happy_eyeballs;
+pub use crate::event::Event;
+mod client;
+pub use crate::client::Client;
+mod component;
+pub use crate::component::Component;
+mod error;
+pub use crate::error::{AuthError, ConnecterError, Error, ParseError, ParserError, ProtocolError};
@@ -0,0 +1,114 @@
+use futures::sink;
+use futures::stream::Stream;
+use futures::{Async, Future, Poll, Sink};
+use xmpp_parsers::{Jid, Element};
+use native_tls::TlsConnector as NativeTlsConnector;
+use std::mem::replace;
+use tokio_io::{AsyncRead, AsyncWrite};
+use tokio_tls::{Connect, TlsConnector, TlsStream};
+
+use crate::xmpp_codec::Packet;
+use crate::xmpp_stream::XMPPStream;
+use crate::Error;
+
+/// XMPP TLS XML namespace
+pub const NS_XMPP_TLS: &str = "urn:ietf:params:xml:ns:xmpp-tls";
+
+/// XMPP stream that switches to TLS if available in received features
+pub struct StartTlsClient<S: AsyncRead + AsyncWrite> {
+ state: StartTlsClientState<S>,
+ jid: Jid,
+}
+
+enum StartTlsClientState<S: AsyncRead + AsyncWrite> {
+ Invalid,
+ SendStartTls(sink::Send<XMPPStream<S>>),
+ AwaitProceed(XMPPStream<S>),
+ StartingTls(Connect<S>),
+}
+
+impl<S: AsyncRead + AsyncWrite> StartTlsClient<S> {
+ /// Waits for <stream:features>
+ pub fn from_stream(xmpp_stream: XMPPStream<S>) -> Self {
+ let jid = xmpp_stream.jid.clone();
+
+ let nonza = Element::builder("starttls").ns(NS_XMPP_TLS).build();
+ let packet = Packet::Stanza(nonza);
+ let send = xmpp_stream.send(packet);
+
+ StartTlsClient {
+ state: StartTlsClientState::SendStartTls(send),
+ jid,
+ }
+ }
+}
+
+impl<S: AsyncRead + AsyncWrite> Future for StartTlsClient<S> {
+ type Item = TlsStream<S>;
+ type Error = Error;
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ let old_state = replace(&mut self.state, StartTlsClientState::Invalid);
+ let mut retry = false;
+
+ let (new_state, result) = match old_state {
+ StartTlsClientState::SendStartTls(mut send) => match send.poll() {
+ Ok(Async::Ready(xmpp_stream)) => {
+ let new_state = StartTlsClientState::AwaitProceed(xmpp_stream);
+ retry = true;
+ (new_state, Ok(Async::NotReady))
+ }
+ Ok(Async::NotReady) => {
+ (StartTlsClientState::SendStartTls(send), Ok(Async::NotReady))
+ }
+ Err(e) => (StartTlsClientState::SendStartTls(send), Err(e.into())),
+ },
+ StartTlsClientState::AwaitProceed(mut xmpp_stream) => match xmpp_stream.poll() {
+ Ok(Async::Ready(Some(Packet::Stanza(ref stanza))))
+ if stanza.name() == "proceed" =>
+ {
+ let stream = xmpp_stream.stream.into_inner();
+ let connect =
+ TlsConnector::from(NativeTlsConnector::builder().build().unwrap())
+ .connect(&self.jid.clone().domain(), stream);
+ let new_state = StartTlsClientState::StartingTls(connect);
+ retry = true;
+ (new_state, Ok(Async::NotReady))
+ }
+ Ok(Async::Ready(_value)) => {
+ // println!("StartTlsClient ignore {:?}", _value);
+ (
+ StartTlsClientState::AwaitProceed(xmpp_stream),
+ Ok(Async::NotReady),
+ )
+ }
+ Ok(_) => (
+ StartTlsClientState::AwaitProceed(xmpp_stream),
+ Ok(Async::NotReady),
+ ),
+ Err(e) => (
+ StartTlsClientState::AwaitProceed(xmpp_stream),
+ Err(Error::Protocol(e.into())),
+ ),
+ },
+ StartTlsClientState::StartingTls(mut connect) => match connect.poll() {
+ Ok(Async::Ready(tls_stream)) => {
+ (StartTlsClientState::Invalid, Ok(Async::Ready(tls_stream)))
+ }
+ Ok(Async::NotReady) => (
+ StartTlsClientState::StartingTls(connect),
+ Ok(Async::NotReady),
+ ),
+ Err(e) => (StartTlsClientState::Invalid, Err(e.into())),
+ },
+ StartTlsClientState::Invalid => unreachable!(),
+ };
+
+ self.state = new_state;
+ if retry {
+ self.poll()
+ } else {
+ result
+ }
+ }
+}
@@ -0,0 +1,125 @@
+use futures::{sink, Async, Future, Poll, Sink, Stream};
+use xmpp_parsers::{Jid, Element};
+use std::mem::replace;
+use tokio_codec::Framed;
+use tokio_io::{AsyncRead, AsyncWrite};
+
+use crate::xmpp_codec::{Packet, XMPPCodec};
+use crate::xmpp_stream::XMPPStream;
+use crate::{Error, ProtocolError};
+
+const NS_XMPP_STREAM: &str = "http://etherx.jabber.org/streams";
+
+pub struct StreamStart<S: AsyncWrite> {
+ state: StreamStartState<S>,
+ jid: Jid,
+ ns: String,
+}
+
+enum StreamStartState<S: AsyncWrite> {
+ SendStart(sink::Send<Framed<S, XMPPCodec>>),
+ RecvStart(Framed<S, XMPPCodec>),
+ RecvFeatures(Framed<S, XMPPCodec>, String),
+ Invalid,
+}
+
+impl<S: AsyncWrite> StreamStart<S> {
+ pub fn from_stream(stream: Framed<S, XMPPCodec>, jid: Jid, ns: String) -> Self {
+ let attrs = [
+ ("to".to_owned(), jid.clone().domain()),
+ ("version".to_owned(), "1.0".to_owned()),
+ ("xmlns".to_owned(), ns.clone()),
+ ("xmlns:stream".to_owned(), NS_XMPP_STREAM.to_owned()),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ let send = stream.send(Packet::StreamStart(attrs));
+
+ StreamStart {
+ state: StreamStartState::SendStart(send),
+ jid,
+ ns,
+ }
+ }
+}
+
+impl<S: AsyncRead + AsyncWrite> Future for StreamStart<S> {
+ type Item = XMPPStream<S>;
+ type Error = Error;
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ let old_state = replace(&mut self.state, StreamStartState::Invalid);
+ let mut retry = false;
+
+ let (new_state, result) = match old_state {
+ StreamStartState::SendStart(mut send) => match send.poll() {
+ Ok(Async::Ready(stream)) => {
+ retry = true;
+ (StreamStartState::RecvStart(stream), Ok(Async::NotReady))
+ }
+ Ok(Async::NotReady) => (StreamStartState::SendStart(send), Ok(Async::NotReady)),
+ Err(e) => (StreamStartState::Invalid, Err(e.into())),
+ },
+ StreamStartState::RecvStart(mut stream) => match stream.poll() {
+ Ok(Async::Ready(Some(Packet::StreamStart(stream_attrs)))) => {
+ let stream_ns = stream_attrs
+ .get("xmlns")
+ .ok_or(ProtocolError::NoStreamNamespace)?
+ .clone();
+ if self.ns == "jabber:client" {
+ retry = true;
+ // TODO: skip RecvFeatures for version < 1.0
+ (
+ StreamStartState::RecvFeatures(stream, stream_ns),
+ Ok(Async::NotReady),
+ )
+ } else {
+ let id = stream_attrs
+ .get("id")
+ .ok_or(ProtocolError::NoStreamId)?
+ .clone();
+ // FIXME: huge hack, shouldnβt be an element!
+ let stream = XMPPStream::new(
+ self.jid.clone(),
+ stream,
+ self.ns.clone(),
+ Element::builder(id).build(),
+ );
+ (StreamStartState::Invalid, Ok(Async::Ready(stream)))
+ }
+ }
+ Ok(Async::Ready(_)) => return Err(ProtocolError::InvalidToken.into()),
+ Ok(Async::NotReady) => (StreamStartState::RecvStart(stream), Ok(Async::NotReady)),
+ Err(e) => return Err(ProtocolError::from(e).into()),
+ },
+ StreamStartState::RecvFeatures(mut stream, stream_ns) => match stream.poll() {
+ Ok(Async::Ready(Some(Packet::Stanza(stanza)))) => {
+ if stanza.is("features", NS_XMPP_STREAM) {
+ let stream =
+ XMPPStream::new(self.jid.clone(), stream, self.ns.clone(), stanza);
+ (StreamStartState::Invalid, Ok(Async::Ready(stream)))
+ } else {
+ (
+ StreamStartState::RecvFeatures(stream, stream_ns),
+ Ok(Async::NotReady),
+ )
+ }
+ }
+ Ok(Async::Ready(_)) | Ok(Async::NotReady) => (
+ StreamStartState::RecvFeatures(stream, stream_ns),
+ Ok(Async::NotReady),
+ ),
+ Err(e) => return Err(ProtocolError::from(e).into()),
+ },
+ StreamStartState::Invalid => unreachable!(),
+ };
+
+ self.state = new_state;
+ if retry {
+ self.poll()
+ } else {
+ result
+ }
+ }
+}
@@ -0,0 +1,532 @@
+//! XML stream parser for XMPP
+
+use crate::{ParseError, ParserError};
+use bytes::{BufMut, BytesMut};
+use xmpp_parsers::Element;
+use quick_xml::Writer as EventWriter;
+use std;
+use std::cell::RefCell;
+use std::collections::vec_deque::VecDeque;
+use std::collections::HashMap;
+use std::default::Default;
+use std::fmt::Write;
+use std::io;
+use std::iter::FromIterator;
+use std::rc::Rc;
+use std::str::from_utf8;
+use std::borrow::Cow;
+use tokio_codec::{Decoder, Encoder};
+use xml5ever::interface::Attribute;
+use xml5ever::tokenizer::{Tag, TagKind, Token, TokenSink, XmlTokenizer};
+use xml5ever::buffer_queue::BufferQueue;
+
+/// Anything that can be sent or received on an XMPP/XML stream
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Packet {
+ /// `<stream:stream>` start tag
+ StreamStart(HashMap<String, String>),
+ /// A complete stanza or nonza
+ Stanza(Element),
+ /// Plain text (think whitespace keep-alive)
+ Text(String),
+ /// `</stream:stream>` closing tag
+ StreamEnd,
+}
+
+type QueueItem = Result<Packet, ParserError>;
+
+/// Parser state
+struct ParserSink {
+ // Ready stanzas, shared with XMPPCodec
+ queue: Rc<RefCell<VecDeque<QueueItem>>>,
+ // Parsing stack
+ stack: Vec<Element>,
+ ns_stack: Vec<HashMap<Option<String>, String>>,
+}
+
+impl ParserSink {
+ pub fn new(queue: Rc<RefCell<VecDeque<QueueItem>>>) -> Self {
+ ParserSink {
+ queue,
+ stack: vec![],
+ ns_stack: vec![],
+ }
+ }
+
+ fn push_queue(&self, pkt: Packet) {
+ self.queue.borrow_mut().push_back(Ok(pkt));
+ }
+
+ fn push_queue_error(&self, e: ParserError) {
+ self.queue.borrow_mut().push_back(Err(e));
+ }
+
+ /// Lookup XML namespace declaration for given prefix (or no prefix)
+ fn lookup_ns(&self, prefix: &Option<String>) -> Option<&str> {
+ for nss in self.ns_stack.iter().rev() {
+ if let Some(ns) = nss.get(prefix) {
+ return Some(ns);
+ }
+ }
+
+ None
+ }
+
+ fn handle_start_tag(&mut self, tag: Tag) {
+ let mut nss = HashMap::new();
+ let is_prefix_xmlns = |attr: &Attribute| {
+ attr.name
+ .prefix
+ .as_ref()
+ .map(|prefix| prefix.eq_str_ignore_ascii_case("xmlns"))
+ .unwrap_or(false)
+ };
+ for attr in &tag.attrs {
+ match attr.name.local.as_ref() {
+ "xmlns" => {
+ nss.insert(None, attr.value.as_ref().to_owned());
+ }
+ prefix if is_prefix_xmlns(attr) => {
+ nss.insert(Some(prefix.to_owned()), attr.value.as_ref().to_owned());
+ }
+ _ => (),
+ }
+ }
+ self.ns_stack.push(nss);
+
+ let el = {
+ let mut el_builder = Element::builder(tag.name.local.as_ref());
+ if let Some(el_ns) =
+ self.lookup_ns(&tag.name.prefix.map(|prefix| prefix.as_ref().to_owned()))
+ {
+ el_builder = el_builder.ns(el_ns);
+ }
+ for attr in &tag.attrs {
+ match attr.name.local.as_ref() {
+ "xmlns" => (),
+ _ if is_prefix_xmlns(attr) => (),
+ _ => {
+ let attr_name = if let Some(ref prefix) = attr.name.prefix {
+ Cow::Owned(format!("{}:{}", prefix, attr.name.local))
+ } else {
+ Cow::Borrowed(attr.name.local.as_ref())
+ };
+ el_builder = el_builder.attr(attr_name, attr.value.as_ref());
+ }
+ }
+ }
+ el_builder.build()
+ };
+
+ if self.stack.is_empty() {
+ let attrs = HashMap::from_iter(tag.attrs.iter().map(|attr| {
+ (
+ attr.name.local.as_ref().to_owned(),
+ attr.value.as_ref().to_owned(),
+ )
+ }));
+ self.push_queue(Packet::StreamStart(attrs));
+ }
+
+ self.stack.push(el);
+ }
+
+ fn handle_end_tag(&mut self) {
+ let el = self.stack.pop().unwrap();
+ self.ns_stack.pop();
+
+ match self.stack.len() {
+ // </stream:stream>
+ 0 => self.push_queue(Packet::StreamEnd),
+ // </stanza>
+ 1 => self.push_queue(Packet::Stanza(el)),
+ len => {
+ let parent = &mut self.stack[len - 1];
+ parent.append_child(el);
+ }
+ }
+ }
+}
+
+impl TokenSink for ParserSink {
+ fn process_token(&mut self, token: Token) {
+ match token {
+ Token::TagToken(tag) => match tag.kind {
+ TagKind::StartTag => self.handle_start_tag(tag),
+ TagKind::EndTag => self.handle_end_tag(),
+ TagKind::EmptyTag => {
+ self.handle_start_tag(tag);
+ self.handle_end_tag();
+ }
+ TagKind::ShortTag => self.push_queue_error(ParserError::ShortTag),
+ },
+ Token::CharacterTokens(tendril) => match self.stack.len() {
+ 0 | 1 => self.push_queue(Packet::Text(tendril.into())),
+ len => {
+ let el = &mut self.stack[len - 1];
+ el.append_text_node(tendril);
+ }
+ },
+ Token::EOFToken => self.push_queue(Packet::StreamEnd),
+ Token::ParseError(s) => {
+ // println!("ParseError: {:?}", s);
+ self.push_queue_error(ParserError::Parse(ParseError(s)));
+ }
+ _ => (),
+ }
+ }
+
+ // fn end(&mut self) {
+ // }
+}
+
+/// Stateful encoder/decoder for a bytestream from/to XMPP `Packet`
+pub struct XMPPCodec {
+ /// Outgoing
+ ns: Option<String>,
+ /// Incoming
+ parser: XmlTokenizer<ParserSink>,
+ /// For handling incoming truncated utf8
+ // TODO: optimize using tendrils?
+ buf: Vec<u8>,
+ /// Shared with ParserSink
+ queue: Rc<RefCell<VecDeque<QueueItem>>>,
+}
+
+impl XMPPCodec {
+ /// Constructor
+ pub fn new() -> Self {
+ let queue = Rc::new(RefCell::new(VecDeque::new()));
+ let sink = ParserSink::new(queue.clone());
+ // TODO: configure parser?
+ let parser = XmlTokenizer::new(sink, Default::default());
+ XMPPCodec {
+ ns: None,
+ parser,
+ queue,
+ buf: vec![],
+ }
+ }
+}
+
+impl Default for XMPPCodec {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Decoder for XMPPCodec {
+ type Item = Packet;
+ type Error = ParserError;
+
+ fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
+ let buf1: Box<dyn AsRef<[u8]>> = if !self.buf.is_empty() && !buf.is_empty() {
+ let mut prefix = std::mem::replace(&mut self.buf, vec![]);
+ prefix.extend_from_slice(buf.take().as_ref());
+ Box::new(prefix)
+ } else {
+ Box::new(buf.take())
+ };
+ let buf1 = buf1.as_ref().as_ref();
+ match from_utf8(buf1) {
+ Ok(mut s) => {
+ s = s.trim();
+ if !s.is_empty() {
+ // println!("<< {}", s);
+ let mut buffer_queue = BufferQueue::new();
+ let tendril = FromIterator::from_iter(s.chars());
+ buffer_queue.push_back(tendril);
+ self.parser.feed(&mut buffer_queue);
+ }
+ }
+ // Remedies for truncated utf8
+ Err(e) if e.valid_up_to() >= buf1.len() - 3 => {
+ // Prepare all the valid data
+ let mut b = BytesMut::with_capacity(e.valid_up_to());
+ b.put(&buf1[0..e.valid_up_to()]);
+
+ // Retry
+ let result = self.decode(&mut b);
+
+ // Keep the tail back in
+ self.buf.extend_from_slice(&buf1[e.valid_up_to()..]);
+
+ return result;
+ }
+ Err(e) => {
+ // println!("error {} at {}/{} in {:?}", e, e.valid_up_to(), buf1.len(), buf1);
+ return Err(ParserError::Utf8(e));
+ }
+ }
+
+ match self.queue.borrow_mut().pop_front() {
+ None => Ok(None),
+ Some(result) => result.map(|pkt| Some(pkt)),
+ }
+ }
+
+ fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
+ self.decode(buf)
+ }
+}
+
+impl Encoder for XMPPCodec {
+ type Item = Packet;
+ type Error = io::Error;
+
+ fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
+ let remaining = dst.capacity() - dst.len();
+ let max_stanza_size: usize = 2usize.pow(16);
+ if remaining < max_stanza_size {
+ dst.reserve(max_stanza_size - remaining);
+ }
+
+ fn to_io_err<E: Into<Box<dyn std::error::Error + Send + Sync>>>(e: E) -> io::Error {
+ io::Error::new(io::ErrorKind::InvalidInput, e)
+ }
+
+ match item {
+ Packet::StreamStart(start_attrs) => {
+ let mut buf = String::new();
+ write!(buf, "<stream:stream")
+ .map_err(to_io_err)?;
+ for (name, value) in start_attrs {
+ write!(buf, " {}=\"{}\"", escape(&name), escape(&value))
+ .map_err(to_io_err)?;
+ if name == "xmlns" {
+ self.ns = Some(value);
+ }
+ }
+ write!(buf, ">\n")
+ .map_err(to_io_err)?;
+
+ // print!(">> {}", buf);
+ write!(dst, "{}", buf)
+ .map_err(to_io_err)
+ }
+ Packet::Stanza(stanza) => {
+ stanza
+ .write_to_inner(&mut EventWriter::new(WriteBytes::new(dst)))
+ .and_then(|_| {
+ // println!(">> {:?}", dst);
+ Ok(())
+ })
+ .map_err(|e| to_io_err(format!("{}", e)))
+ }
+ Packet::Text(text) => {
+ write_text(&text, dst)
+ .and_then(|_| {
+ // println!(">> {:?}", dst);
+ Ok(())
+ })
+ .map_err(to_io_err)
+ }
+ Packet::StreamEnd => {
+ write!(dst, "</stream:stream>\n")
+ .map_err(to_io_err)
+ }
+ }
+ }
+}
+
+/// Write XML-escaped text string
+pub fn write_text<W: Write>(text: &str, writer: &mut W) -> Result<(), std::fmt::Error> {
+ write!(writer, "{}", escape(text))
+}
+
+/// Copied from `RustyXML` for now
+pub fn escape(input: &str) -> String {
+ let mut result = String::with_capacity(input.len());
+
+ for c in input.chars() {
+ match c {
+ '&' => result.push_str("&"),
+ '<' => result.push_str("<"),
+ '>' => result.push_str(">"),
+ '\'' => result.push_str("'"),
+ '"' => result.push_str("""),
+ o => result.push(o),
+ }
+ }
+ result
+}
+
+/// BytesMut impl only std::fmt::Write but not std::io::Write. The
+/// latter trait is required for minidom's
+/// `Element::write_to_inner()`.
+struct WriteBytes<'a> {
+ dst: &'a mut BytesMut,
+}
+
+impl<'a> WriteBytes<'a> {
+ fn new(dst: &'a mut BytesMut) -> Self {
+ WriteBytes { dst }
+ }
+}
+
+impl<'a> std::io::Write for WriteBytes<'a> {
+ fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, std::io::Error> {
+ self.dst.put_slice(buf);
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use bytes::BytesMut;
+
+ #[test]
+ fn test_stream_start() {
+ let mut c = XMPPCodec::new();
+ let mut b = BytesMut::with_capacity(1024);
+ b.put(r"<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xmlns='jabber:client'>");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(Some(Packet::StreamStart(_))) => true,
+ _ => false,
+ });
+ }
+
+ #[test]
+ fn test_stream_end() {
+ let mut c = XMPPCodec::new();
+ let mut b = BytesMut::with_capacity(1024);
+ b.put(r"<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xmlns='jabber:client'>");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(Some(Packet::StreamStart(_))) => true,
+ _ => false,
+ });
+ b.clear();
+ b.put(r"</stream:stream>");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(Some(Packet::StreamEnd)) => true,
+ _ => false,
+ });
+ }
+
+ #[test]
+ fn test_truncated_stanza() {
+ let mut c = XMPPCodec::new();
+ let mut b = BytesMut::with_capacity(1024);
+ b.put(r"<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xmlns='jabber:client'>");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(Some(Packet::StreamStart(_))) => true,
+ _ => false,
+ });
+
+ b.clear();
+ b.put(r"<test>Γ</test");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(None) => true,
+ _ => false,
+ });
+
+ b.clear();
+ b.put(r">");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(Some(Packet::Stanza(ref el))) if el.name() == "test" && el.text() == "Γ" => true,
+ _ => false,
+ });
+ }
+
+ #[test]
+ fn test_truncated_utf8() {
+ let mut c = XMPPCodec::new();
+ let mut b = BytesMut::with_capacity(1024);
+ b.put(r"<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xmlns='jabber:client'>");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(Some(Packet::StreamStart(_))) => true,
+ _ => false,
+ });
+
+ b.clear();
+ b.put(&b"<test>\xc3"[..]);
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(None) => true,
+ _ => false,
+ });
+
+ b.clear();
+ b.put(&b"\x9f</test>"[..]);
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(Some(Packet::Stanza(ref el))) if el.name() == "test" && el.text() == "Γ" => true,
+ _ => false,
+ });
+ }
+
+ /// test case for https://gitlab.com/xmpp-rs/tokio-xmpp/issues/3
+ #[test]
+ fn test_atrribute_prefix() {
+ let mut c = XMPPCodec::new();
+ let mut b = BytesMut::with_capacity(1024);
+ b.put(r"<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xmlns='jabber:client'>");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(Some(Packet::StreamStart(_))) => true,
+ _ => false,
+ });
+
+ b.clear();
+ b.put(r"<status xml:lang='en'>Test status</status>");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(Some(Packet::Stanza(ref el))) if el.name() == "status" && el.text() == "Test status" && el.attr("xml:lang").map_or(false, |a| a == "en") => true,
+ _ => false,
+ });
+
+ }
+
+ /// By default, encode() only get's a BytesMut that has 8kb space reserved.
+ #[test]
+ fn test_large_stanza() {
+ use futures::{Future, Sink};
+ use std::io::Cursor;
+ use tokio_codec::FramedWrite;
+ let framed = FramedWrite::new(Cursor::new(vec![]), XMPPCodec::new());
+ let mut text = "".to_owned();
+ for _ in 0..2usize.pow(15) {
+ text = text + "A";
+ }
+ let stanza = Element::builder("message")
+ .append(Element::builder("body").append(text.as_ref()).build())
+ .build();
+ let framed = framed.send(Packet::Stanza(stanza)).wait().expect("send");
+ assert_eq!(
+ framed.get_ref().get_ref(),
+ &("<message><body>".to_owned() + &text + "</body></message>").as_bytes()
+ );
+ }
+
+ #[test]
+ fn test_lone_whitespace() {
+ let mut c = XMPPCodec::new();
+ let mut b = BytesMut::with_capacity(1024);
+ b.put(r"<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xmlns='jabber:client'>");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(Some(Packet::StreamStart(_))) => true,
+ _ => false,
+ });
+
+ b.clear();
+ b.put(r" ");
+ let r = c.decode(&mut b);
+ assert!(match r {
+ Ok(None) => true,
+ _ => false,
+ });
+ }
+}
@@ -0,0 +1,92 @@
+//! `XMPPStream` is the common container for all XMPP network connections
+
+use futures::sink::Send;
+use futures::{Poll, Sink, StartSend, Stream};
+use xmpp_parsers::{Jid, Element};
+use tokio_codec::Framed;
+use tokio_io::{AsyncRead, AsyncWrite};
+
+use crate::stream_start::StreamStart;
+use crate::xmpp_codec::{Packet, XMPPCodec};
+
+/// <stream:stream> namespace
+pub const NS_XMPP_STREAM: &str = "http://etherx.jabber.org/streams";
+
+/// Wraps a `stream`
+pub struct XMPPStream<S> {
+ /// The local Jabber-Id
+ pub jid: Jid,
+ /// Codec instance
+ pub stream: Framed<S, XMPPCodec>,
+ /// `<stream:features/>` for XMPP version 1.0
+ pub stream_features: Element,
+ /// Root namespace
+ ///
+ /// This is different for either c2s, s2s, or component
+ /// connections.
+ pub ns: String,
+}
+
+impl<S: AsyncRead + AsyncWrite> XMPPStream<S> {
+ /// Constructor
+ pub fn new(
+ jid: Jid,
+ stream: Framed<S, XMPPCodec>,
+ ns: String,
+ stream_features: Element,
+ ) -> Self {
+ XMPPStream {
+ jid,
+ stream,
+ stream_features,
+ ns,
+ }
+ }
+
+ /// Send a `<stream:stream>` start tag
+ pub fn start(stream: S, jid: Jid, ns: String) -> StreamStart<S> {
+ let xmpp_stream = Framed::new(stream, XMPPCodec::new());
+ StreamStart::from_stream(xmpp_stream, jid, ns)
+ }
+
+ /// Unwraps the inner stream
+ pub fn into_inner(self) -> S {
+ self.stream.into_inner()
+ }
+
+ /// Re-run `start()`
+ pub fn restart(self) -> StreamStart<S> {
+ Self::start(self.stream.into_inner(), self.jid, self.ns)
+ }
+}
+
+impl<S: AsyncWrite> XMPPStream<S> {
+ /// Convenience method
+ pub fn send_stanza<E: Into<Element>>(self, e: E) -> Send<Self> {
+ self.send(Packet::Stanza(e.into()))
+ }
+}
+
+/// Proxy to self.stream
+impl<S: AsyncWrite> Sink for XMPPStream<S> {
+ type SinkItem = <Framed<S, XMPPCodec> as Sink>::SinkItem;
+ type SinkError = <Framed<S, XMPPCodec> as Sink>::SinkError;
+
+ fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
+ self.stream.start_send(item)
+ }
+
+ fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
+ self.stream.poll_complete()
+ }
+}
+
+/// Proxy to self.stream
+impl<S: AsyncRead> Stream for XMPPStream<S> {
+ type Item = <Framed<S, XMPPCodec> as Stream>::Item;
+ type Error = <Framed<S, XMPPCodec> as Stream>::Error;
+
+ fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
+ self.stream.poll()
+ }
+}