├── .github └── workflows │ ├── ci.yml │ ├── clippy-fmt.yml │ └── update-doc.yml ├── .gitignore ├── .rustfmt.toml ├── Cargo.toml ├── PROTOCOL.md ├── README.md ├── credentials ├── Cargo.toml └── src │ └── lib.rs ├── docker-compose.yml ├── examples └── server │ ├── Cargo.toml │ ├── config │ └── default.toml │ ├── server.crt │ ├── server.key │ └── src │ └── main.rs ├── proto ├── Cargo.toml └── src │ ├── lib.rs │ ├── non_stanza.rs │ ├── non_stanza │ ├── auth.rs │ ├── bind.rs │ ├── close_stream.rs │ ├── open_stream.rs │ ├── proceed_tls.rs │ ├── sasl_success.rs │ ├── start_tls.rs │ ├── stream_error.rs │ └── stream_features.rs │ ├── ns.rs │ ├── packet.rs │ ├── stanza.rs │ └── stanza │ ├── generic_iq.rs │ ├── message.rs │ └── presence.rs ├── server ├── Cargo.toml └── src │ ├── authentication.rs │ ├── config.rs │ ├── lib.rs │ ├── listeners.rs │ ├── listeners │ ├── tcp.rs │ ├── tcp │ │ ├── listener.rs │ │ └── session.rs │ └── ws.rs │ ├── messages.rs │ ├── messages │ ├── system.rs │ └── tcp.rs │ ├── packet.rs │ ├── parser.rs │ ├── parser │ ├── codec.rs │ └── sink.rs │ ├── router.rs │ ├── sessions.rs │ ├── sessions │ ├── manager.rs │ ├── state.rs │ └── unauthenticated.rs │ ├── tests.rs │ └── tests │ ├── executor.rs │ ├── fixtures │ ├── server.crt │ └── server.key │ ├── rfc6120.rs │ └── rfc6120 │ ├── namespaces.rs │ ├── starttls.rs │ └── stream_attribute.rs ├── src └── lib.rs └── xml ├── Cargo.toml ├── src ├── children.rs ├── element.rs ├── error.rs ├── lib.rs ├── libold.rs ├── namespace.rs ├── options.rs ├── position.rs ├── qname.rs └── xml_atom.rs └── tests └── test_basic.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | build_and_test: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | target: 11 | - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } 12 | - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } 13 | - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } 14 | version: 15 | - stable 16 | - nightly 17 | 18 | name: ${{ matrix.target.name }} / ${{ matrix.version }} 19 | runs-on: ${{ matrix.target.os }} 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Install ${{ matrix.version }} 25 | uses: actions-rs/toolchain@v1 26 | with: 27 | toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} 28 | profile: minimal 29 | override: true 30 | 31 | - name: Generate Cargo.lock 32 | uses: actions-rs/cargo@v1 33 | with: 34 | command: generate-lockfile 35 | - name: Cache Dependencies 36 | uses: Swatinem/rust-cache@v1.2.0 37 | 38 | - name: Install cargo-hack 39 | uses: actions-rs/cargo@v1 40 | with: 41 | command: install 42 | args: cargo-hack 43 | 44 | - name: check minimal 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: hack 48 | args: --clean-per-run check --workspace --no-default-features --tests 49 | 50 | - name: check full 51 | uses: actions-rs/cargo@v1 52 | with: 53 | command: check 54 | args: --workspace --bins --examples --tests 55 | 56 | - name: tests 57 | uses: actions-rs/cargo@v1 58 | with: 59 | command: test 60 | args: -v --workspace --all-features --no-fail-fast -- --nocapture 61 | 62 | - name: Generate coverage file 63 | if: > 64 | matrix.target.os == 'ubuntu-latest' 65 | && matrix.version == 'stable' 66 | && github.ref == 'refs/heads/master' 67 | run: | 68 | cargo install cargo-tarpaulin --vers "^0.13" 69 | cargo tarpaulin --all --out Xml --verbose 70 | - name: Upload to Codecov 71 | if: > 72 | matrix.target.os == 'ubuntu-latest' 73 | && matrix.version == 'stable' 74 | && github.ref == 'refs/heads/master' 75 | uses: codecov/codecov-action@v1 76 | with: 77 | file: cobertura.xml 78 | 79 | - name: Clear the cargo caches 80 | run: | 81 | cargo install cargo-cache --no-default-features --features ci-autoclean 82 | cargo-cache 83 | 84 | -------------------------------------------------------------------------------- /.github/workflows/clippy-fmt.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | jobs: 8 | fmt: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Install Rust 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | components: rustfmt 18 | - name: Check with rustfmt 19 | uses: actions-rs/cargo@v1 20 | with: 21 | command: fmt 22 | args: --all -- --check 23 | 24 | clippy: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v2 28 | 29 | - name: Install Rust 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | toolchain: stable 33 | components: clippy 34 | override: true 35 | - name: Check with Clippy 36 | uses: actions-rs/clippy-check@v1 37 | with: 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | args: --workspace --tests --all-features 40 | -------------------------------------------------------------------------------- /.github/workflows/update-doc.yml: -------------------------------------------------------------------------------- 1 | name: Upload Documentation 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Install Rust 15 | uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: nightly-x86_64-unknown-linux-gnu 18 | profile: minimal 19 | override: true 20 | 21 | - name: Build Docs 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: doc 25 | args: --workspace --all-features --no-deps 26 | 27 | - name: Tweak HTML 28 | run: echo '' > target/doc/index.html 29 | 30 | - name: Deploy to GitHub Pages 31 | uses: JamesIves/github-pages-deploy-action@3.7.1 32 | with: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | BRANCH: gh-pages 35 | FOLDER: target/doc 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 200 2 | format_macro_matchers = true 3 | format_macro_bodies = true 4 | use_try_shorthand = true 5 | unstable_features = true 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xmpp-rs" 3 | version = "0.1.3" 4 | description = """ 5 | xmpp-rs is an implementation of the Extensible Messaging and Presence Protocol (XMPP). 6 | 7 | Based on tokio-rs and futures-rs. It's goal is to be fully tested and usable. 8 | """ 9 | 10 | edition = "2018" 11 | license = "MPL-2.0" 12 | documentation = "https://docs.rs/xmpp-rs" 13 | authors = ["Freyskeyd "] 14 | repository = "https://github.com/Freyskeyd/xmpp-rs" 15 | readme = "README.md" 16 | keywords = ["xmpp", "tokio", "jabber", "IM", "instant-messaging"] 17 | categories = ["network-programming"] 18 | exclude = [ 19 | "derive", 20 | ] 21 | [badges] 22 | travis-ci = { repository = "freyskeyd/xmpp-rs", branch = "master" } 23 | 24 | [lib] 25 | name = "xmpp" 26 | path = "src/lib.rs" 27 | 28 | [dev-dependencies] 29 | env_logger = "0" 30 | futures = "0.1" 31 | log = "0" 32 | tokio-core = "0.1" 33 | 34 | xml-rs = "*" 35 | circular = "*" 36 | 37 | [dependencies] 38 | xmpp-credentials = { path = "credentials" } 39 | xmpp-proto = { path = "proto" } 40 | xmpp-server = { path = "server" } 41 | 42 | [workspace] 43 | members = ["proto", "credentials/", "server/", "examples/server"] 44 | 45 | -------------------------------------------------------------------------------- /PROTOCOL.md: -------------------------------------------------------------------------------- 1 | # PROTOCOL 2 | 3 | - [ ] [RFC-6120](https://datatracker.ietf.org/doc/rfc6120/) 4 |
5 | 6 | Implementation details 7 | 8 | 9 | ``` 10 | C: = a client 11 | E: = any XMPP entity 12 | I: = an initiating entity 13 | P: = a peer server 14 | R: = a receiving entity 15 | S: = a server 16 | S1: = server1 17 | S2: = server2 18 | ``` 19 | 20 | - [x] TCP Binding 21 | - [ ] XML Streams 22 | - [x] Opening a Stream 23 | - [ ] Stream Negotiation 24 | - [x] Restarts 25 | - [x] Resending Features 26 | - [ ] Completion of Stream Negotiation 27 | - [ ] Determination of Addresses 28 | - [ ] Closing a Stream 29 | - [ ] Directionality 30 | - [ ] Handling of Silent Peers 31 | - [ ] Dead Connection 32 | - [ ] Broken Stream 33 | - [ ] Idle Peer 34 | - [ ] Use of Checking Methods 35 | - [ ] Stream Attributes 36 | - [ ] from 37 | - [x] to 38 | - [x] id 39 | - [ ] xml:lang 40 | - [x] version 41 | - [ ] XML Namespaces 42 | - [ ] Stream Namespace 43 | - [ ] Content Namespace 44 | - [ ] XMPP Content Namespaces 45 | - [ ] Other Namespaces 46 | - [ ] Namespace Declarations and Prefixes 47 | - [ ] Stream Errors 48 | - [x] Rules 49 | - [x] Stream Errors Are Unrecoverable 50 | - [x] Stream Errors Can Occur During Setup 51 | - [x] Stream Errors When the Host Is Unspecified or Unknown 52 | - [x] Where Stream Errors Are Sent 53 | - [x] Syntax 54 | - [ ] Stream Error Conditions 55 | - [ ] bad-format -> not applied 56 | - [x] bad-namespace-prefix 57 | - [ ] conflict 58 | - [ ] connection-timeout 59 | - [ ] host-gone 60 | - [x] host-unknown 61 | - [ ] improper-addressing 62 | - [ ] internal-server-error 63 | - [ ] invalid-from 64 | - [x] invalid-namespace 65 | - [ ] invalid-xml 66 | - [x] not-authorized 67 | - [ ] not-well-formed 68 | - [ ] policy-violation 69 | - [ ] remote-connection-failed 70 | - [ ] reset 71 | - [ ] resource-constraint 72 | - [ ] restricted-xml 73 | - [ ] see-other-host 74 | - [ ] system-shutdown 75 | - [ ] undefined-condition 76 | - [x] unsupported-encoding 77 | - [ ] unsupported-feature 78 | - [ ] unsupported-stanza-type 79 | - [ ] unsupported-version 80 | - [ ] Application-Specific Conditions 81 | - [ ] Simplified Stream Examples 82 | 83 | - [ ] STARTTLS Negotiation 84 | - [x] Fundamentals 85 | - [x] Support 86 | - [ ] Stream Negotiation Rules 87 | - [ ] Mandatory-to-Negotiate 88 | - [ ] Restart 89 | - [ ] Data Formatting 90 | - [ ] Order of TLS and SASL Negotiations 91 | - [ ] TLS Renegotiation 92 | - [ ] TLS Extensions 93 | - [ ] Process 94 | - [ ] Exchange of Stream Headers and Stream Features 95 | - [ ] Initiation of STARTTLS Negotiation 96 | - [ ] STARTTLS Command 97 | - [ ] Failure Case 98 | - [ ] Proceed Case 99 | - [ ] TLS Negotiation 100 | - [ ] Rules 101 | - [ ] TLS Failure 102 | - [ ] TLS Success 103 | - [ ] SASL Negotiation 104 | - [ ] Fundamentals 105 | - [ ] Support 106 | - [ ] Stream Negotiation Rules 107 | - [ ] Mandatory-to-Negotiate 108 | - [ ] Restart 109 | - [ ] Mechanism Preferences 110 | - [ ] Mechanism Offers 111 | - [ ] Data Formatting 112 | - [ ] Security Layers 113 | - [ ] Simple User Name 114 | - [ ] Authorization Identity 115 | - [ ] Realms 116 | - [ ] Round Trips 117 | - [ ] Process 118 | - [ ] Exchange of Stream Headers and Stream Features 119 | - [ ] Initiation 120 | - [ ] Challenge-Response Sequence 121 | - [ ] Abort 122 | - [ ] SASL Failure 123 | - [ ] SASL Success 124 | - [ ] SASL Errors 125 | - [ ] aborted 126 | - [ ] account-disabled 127 | - [ ] credentials-expired 128 | - [ ] encryption-required 129 | - [ ] incorrect-encoding 130 | - [ ] invalid-authzid 131 | - [ ] invalid-mechanism 132 | - [ ] malformed-request 133 | - [ ] mechanism-too-weak 134 | - [ ] not-authorized 135 | - [ ] temporary-auth-failure 136 | - [ ] SASL Definition 137 | - [ ] Resource Binding 138 | - [ ] Fundamentals 139 | - [ ] Support 140 | - [ ] Stream Negotiation Rules 141 | - [ ] Mandatory-to-Negotiate 142 | - [ ] Restart 143 | - [ ] Advertising Support 144 | - [ ] Generation of Resource Identifiers 145 | - [ ] Server-Generated Resource Identifier 146 | - [ ] Success Case 147 | - [ ] Error Cases 148 | - [ ] Resource Constraint 149 | - [ ] Not Allowed 150 | - [ ] Client-Submitted Resource Identifier 151 | - [ ] Success Case 152 | - [ ] Error Cases 153 | - [ ] Bad Request 154 | - [ ] Conflict 155 | - [ ] Retries 156 | - [ ] XML Stanzas 157 | - [ ] Common Attributes 158 | - [ ] to 159 | - [ ] Client-to-Server Streams 160 | - [ ] Server-to-Server Streams 161 | - [ ] from 162 | - [ ] Client-to-Server Streams 163 | - [ ] Server-to-Server Streams 164 | - [ ] id 165 | - [ ] type 166 | - [ ] xml:lang 167 | - [ ] Basic Semantics 168 | - [ ] Message Semantics 169 | - [ ] Presence Semantics 170 | - [ ] IQ Semantics 171 | - [ ] Stanza Errors 172 | - [ ] Rules 173 | - [ ] Syntax 174 | - [ ] Defined Conditions 175 | - [ ] bad-request 176 | - [ ] conflict 177 | - [ ] feature-not-implemented 178 | - [ ] forbidden 179 | - [ ] gone 180 | - [ ] internal-server-error 181 | - [ ] item-not-found 182 | - [ ] jid-malformed 183 | - [ ] not-acceptable 184 | - [ ] not-allowed 185 | - [ ] not-authorized 186 | - [ ] policy-violation 187 | - [ ] recipient-unavailable 188 | - [ ] redirect 189 | - [ ] registration-required 190 | - [ ] remote-server-not-found 191 | - [ ] remote-server-timeout 192 | - [ ] resource-constraint 193 | - [ ] service-unavailable 194 | - [ ] subscription-required 195 | - [ ] undefined-condition 196 | - [ ] unexpected-request 197 | - [ ] Application-Specific Conditions 198 | - [ ] Extended Content 199 | - [ ] Detailed Examples 200 | - [ ] Client-to-Server Examples 201 | - [ ] TLS 202 | - [ ] SASL 203 | - [ ] Resource Binding 204 | - [ ] Stanza Exchange 205 | - [ ] Close 206 | - [ ] Server-to-Server Examples 207 | - [ ] TLS 208 | - [ ] SASL 209 | - [ ] Stanza Exchange 210 | - [ ] Close 211 | - [ ] Server Rules for Processing XML Stanzas 212 | - [ ] In-Order Processing 213 | - [ ] General Considerations 214 | - [ ] No 'to' Address 215 | - [ ] Message 216 | - [ ] Presence 217 | - [ ] IQ 218 | - [ ] Remote Domain 219 | - [ ] Existing Stream 220 | - [ ] No Existing Stream 221 | - [ ] Error Handling 222 | - [ ] Local Domain 223 | - [ ] domainpart 224 | - [ ] domainpart/resourcepart 225 | - [ ] localpart@domainpart 226 | - [ ] No Such User 227 | - [ ] User Exists 228 | - [ ] localpart@domainpart/resourcepart 229 | - [ ] XML Usage 230 | - [ ] XML Restrictions 231 | - [ ] XML Namespace Names and Prefixes 232 | - [ ] Well-Formedness 233 | - [ ] Validation 234 | - [ ] Inclusion of XML Declaration 235 | - [ ] Character Encoding 236 | - [ ] Whitespace 237 | - [ ] XML Versions 238 | - [ ] Internationalization Considerations 239 | - [ ] Security Considerations 240 | - [ ] Fundamentals 241 | - [ ] Threat Model 242 | - [ ] Order of Layers 243 | - [ ] Confidentiality and Integrity 244 | - [ ] Peer Entity Authentication 245 | - [ ] Strong Security 246 | - [ ] Certificates 247 | - [ ] Certificate Generation 248 | - [ ] General Considerations 249 | - [ ] Server Certificates 250 | - [ ] Client Certificates 251 | - [ ] XmppAddr Identifier Type 252 | - [ ] Certificate Validation 253 | - [ ] Server Certificates 254 | - [ ] Client Certificates 255 | - [ ] Checking of Certificates in Long-Lived Streams 256 | - [ ] Use of Certificates in XMPP Extensions 257 | - [ ] Mandatory-to-Implement TLS and SASL Technologies 258 | - [ ] For Authentication Only 259 | - [ ] For Confidentiality Only 260 | - [ ] For Confidentiality and Authentication with Passwords 261 | - [ ] For Confidentiality and Authentication without Passwords 262 | - [ ] Technology Reuse 263 | - [ ] Use of Base 64 in SASL 264 | - [ ] Use of DNS 265 | - [ ] Use of Hash Functions 266 | - [ ] Use of SASL 267 | - [ ] Use of TLS 268 | - [ ] Use of UTF-8 269 | - [ ] Use of XML 270 | - [ ] Information Leaks 271 | - [ ] IP Addresses 272 | - [ ] Presence Information 273 | - [ ] Directory Harvesting 274 | - [ ] Denial of Service 275 | - [ ] Firewalls 276 | - [ ] Interdomain Federation 277 | - [ ] Non-Repudiation 278 | - [ ] IANA Considerations 279 | - [ ] XML Namespace Name for TLS Data 280 | - [ ] XML Namespace Name for SASL Data 281 | - [ ] XML Namespace Name for Stream Errors 282 | - [ ] XML Namespace Name for Resource Binding 283 | - [ ] XML Namespace Name for Stanza Errors 284 | - [ ] GSSAPI Service Name 285 | - [ ] Port Numbers and Service Names 286 | - [ ] Conformance Requirements 287 | - [ ] References 288 | - [ ] Normative References 289 | - [ ] Informative References 290 | > Appendix A. XML Schemas . . . . . . . . . . . . . . . . . . . . 190 291 | > A.1. Stream Namespace . . . . . . . . . . . . . . . . . . . . 190 292 | > A.2. Stream Error Namespace . . . . . . . . . . . . . . . . . 192 293 | > A.3. STARTTLS Namespace . . . . . . . . . . . . . . . . . . . 193 294 | > A.4. SASL Namespace . . . . . . . . . . . . . . . . . . . . . 194 295 | > A.5. Client Namespace . . . . . . . . . . . . . . . . . . . . 196 296 | > A.6. Server Namespace . . . . . . . . . . . . . . . . . . . . 201 297 | > A.7. Resource Binding Namespace . . . . . . . . . . . . . . . 206 298 | > A.8. Stanza Error Namespace . . . . . . . . . . . . . . . . . 206 299 | > Appendix B. Contact Addresses . . . . . . . . . . . . . . . . . 208 300 | > Appendix C. Account Provisioning . . . . . . . . . . . . . . . . 208 301 | > Appendix D. Differences from RFC 3920 . . . . . . . . . . . . . 208 302 | > Appendix E. Acknowledgements . . . . . . . . . . . . . . . . . . 210 303 | 304 |
305 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xmpp-rs 2 | 3 | `xmpp-rs` is an XMPP Server. 4 | 5 | [![CI](https://github.com/Freyskeyd/xmpp-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/Freyskeyd/xmpp-rs/actions/workflows/ci.yml) 6 | [![Upload Documentation](https://github.com/Freyskeyd/xmpp-rs/actions/workflows/update-doc.yml/badge.svg)](https://github.com/Freyskeyd/xmpp-rs/actions/workflows/update-doc.yml) 7 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FFreyskeyd%2Fxmpp-rs.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FFreyskeyd%2Fxmpp-rs?ref=badge_shield) 8 | ## Install 9 | 10 | *Under construction* 11 | 12 | ## Build 13 | 14 | To build xmpp-rs: 15 | 16 | `cargo build --release` 17 | 18 | ## ROADMAP 19 | 20 | - [ ] :rocket: Root implementation 21 | - [ ] **TCP Connection**: Able to accept TCP connection from clients 22 | - [ ] **Open stream reading**: Listen to an open `stream` stanza and respond to it 23 | - [ ] **TLS Connection and negociation** 24 | - [ ] **PLAIN authentication**: Authentification with a PLAIN mechanism must be possible. 25 | 26 | ## XEPs 27 | 28 | - [ ] [RFC-6122: (XMPP): Address Format](https://tools.ietf.org/html/rfc6122) 29 | - [ ] [RFC-7590: Use of TLS](https://tools.ietf.org/html/rfc7590) 30 | - [ ] [XEP-0368: SRV records for XMPP over TLS](https://xmpp.org/extensions/xep-0368.html) 31 | - [ ] [XEP-0199: XMPP Ping](https://xmpp.org/extensions/xep-0199.html) 32 | - [ ] [XEP-0004: Data Forms](https://xmpp.org/extensions/xep-0004.html) 33 | - [ ] [XEP-0030: Service Discovery](https://xmpp.org/extensions/xep-0030.html) 34 | - [ ] [XEP-0048: Bookmarks](https://xmpp.org/extensions/xep-0048.html) 35 | - [ ] [XEP-0049: Private XML Storage](https://xmpp.org/extensions/xep-0049.html) 36 | 37 | - [ ] [Stanza errors](https://tools.ietf.org/html/rfc6120#section-8.3) 38 | 39 | ## License 40 | 41 | xmpp-rs is primarily distributed under the terms of both the MIT license 42 | and the Apache License (Version 2.0), with portions covered by various 43 | BSD-like licenses. 44 | 45 | See LICENSE-APACHE, and LICENSE-MIT for details. 46 | -------------------------------------------------------------------------------- /credentials/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xmpp-credentials" 3 | version = "0.1.3" 4 | license = "MPL-2.0" 5 | 6 | description = """ 7 | xmpp-rs is an implementation of the Extensible Messaging and Presence Protocol (XMPP). 8 | 9 | Based on tokio-rs and futures-rs. It's goal is to be fully tested and usable. 10 | """ 11 | repository = "https://github.com/Freyskeyd/xmpp-rs" 12 | keywords = ["xmpp", "tokio", "jabber", "IM", "instant-messaging"] 13 | categories = ["network-programming"] 14 | 15 | [dependencies] 16 | sasl = "0.4.1" 17 | jid = "0" 18 | -------------------------------------------------------------------------------- /credentials/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate jid; 2 | extern crate sasl; 3 | 4 | use jid::Jid; 5 | use sasl::common::Credentials as SaslCredentials; 6 | use std::str::FromStr; 7 | 8 | /// Define Credentials used to authenticate a user 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub struct Credentials { 11 | pub jid: Jid, 12 | pub password: String, 13 | } 14 | 15 | impl Credentials { 16 | pub fn format(&self) -> SaslCredentials { 17 | SaslCredentials::default().with_username(self.jid.to_string()).with_password(self.password.to_string()) 18 | } 19 | } 20 | 21 | impl Default for Credentials { 22 | fn default() -> Credentials { 23 | Credentials { 24 | jid: Jid::from_str("guest").unwrap(), 25 | password: "guest".to_string(), 26 | } 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | 34 | #[test] 35 | fn check_default_values() { 36 | let c = Credentials { ..Credentials::default() }; 37 | 38 | assert_eq!(c.jid, Jid::from_str("guest").unwrap()); 39 | assert_eq!(c.password, "guest"); 40 | } 41 | 42 | #[test] 43 | fn creation() { 44 | let c = Credentials { 45 | jid: Jid::from_str("guest").unwrap(), 46 | password: "guest".into(), 47 | }; 48 | assert_eq!(c.jid, Jid::from_str("guest").unwrap()); 49 | assert_eq!(c.password, "guest"); 50 | } 51 | 52 | #[test] 53 | fn equality() { 54 | let c = Credentials { 55 | jid: Jid::from_str("guest").unwrap(), 56 | password: "guest".into(), 57 | }; 58 | let mut d = c.clone(); 59 | 60 | assert!(c == d); 61 | 62 | d.jid = Jid::from_str("guest2").unwrap(); 63 | d.password = "guest".into(); 64 | 65 | assert!(c != d); 66 | } 67 | 68 | #[test] 69 | fn format() { 70 | let c = Credentials { 71 | jid: Jid::from_str("guest").unwrap(), 72 | password: "guest".into(), 73 | }; 74 | 75 | let check = SaslCredentials::default().with_username("guest").with_password("guest"); 76 | 77 | assert!(c.format().identity == check.identity); 78 | assert!(c.format().secret == check.secret); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | ejabberd: 2 | image: rroemhild/ejabberd 3 | ports: 4 | - 5222:5222 5 | - 5269:5269 6 | - 5280:5280 7 | volumes: 8 | - ssl:/opt/ejabberd/ssl 9 | environment: 10 | - ERLANG_NODE=ejabberd 11 | - EJABBERD_LOGLEVEL=5 12 | - XMPP_DOMAIN=example.com 13 | # - EJABBERD_ADMINS=admin@example.com 14 | - EJABBERD_USERS=alice@example.com:test user1@example.com:test 15 | -------------------------------------------------------------------------------- /examples/server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server" 3 | version = "0.1.0" 4 | authors = ["Freyskeyd "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | actix = "0.11.0" 11 | xmpp-rs = {path = "../../"} 12 | 13 | env_logger = "*" 14 | -------------------------------------------------------------------------------- /examples/server/config/default.toml: -------------------------------------------------------------------------------- 1 | authenticators = ["sql", "personal"] 2 | 3 | [vhosts.localhost] 4 | name = "locahost" 5 | 6 | [[listeners]] 7 | [listeners.Tcp] 8 | port = 5222 9 | ip = "127.0.0.1" 10 | # starttls = "Unavailable" 11 | 12 | # [[listeners]] 13 | 14 | # [listeners.Tcp] 15 | # port = 5223 16 | # ip = "127.0.0.1" 17 | 18 | [listeners.Tcp.starttls.Required] 19 | cert_path = "server.crt" 20 | key_path = "server.key" 21 | -------------------------------------------------------------------------------- /examples/server/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICljCCAX4CCQCCkuNb7meOCTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJm 3 | cjAeFw0yMTAxMDUwODIwMjRaFw0yMjAxMDUwODIwMjRaMA0xCzAJBgNVBAYTAmZy 4 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzEWH6yE14a5mDgF7aTxd 5 | aDkH42jsEMCO8b2cRGfUrU+eSRk9WCMaSL/zPhiBq7cWYKZqa9ML/myHQCFX8FVn 6 | 8rLuUCDtb+Z4fNwdccHQmiZmQz6nTKdnyz9bvnGyTj5QNnxeOmsxl/9PI32qoM/3 7 | wrRS5CQf22NgKkPGEo/nKXGkd/zIZ856c+ZXBMHnpzUQlogyJSpS/5HU6j97GpKg 8 | Vx/ETeWLLwbjEQVOWbil7uRn2YhfDVdD9G5qu4jmyKIW7v+Mq8O8ccFcJgiCEz3i 9 | 6Gz50YeUim0dRXQA/or9ix/b2eilNtn9q5DCFP2EkaBexpccBW0SLTjZTl+iOyyR 10 | AwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDGOD6IDqymUrIthvt/xoMlEGUTR0QT 11 | X62iP456RXy5s1jYFAU6f/ullQMz0CynSPiWFcShMoUUCG+fjqpDAWiplV7zQKMY 12 | dvctoSfLSyQAENu8UUhNo4s+0j/JdEve4LZENnQoY3GTZtV3qcKT8pgFIN0xJMc+ 13 | MFVNifzv83Buex2Hti/UfBBpXZyIqZLHx0NhZ4VIjj593oQqSYcDWDf6UeVqkkOb 14 | DxLt5WOHwpPWyTFyyp/yMfISXqJw/7n7FjZZJIjjP5I4T1y58lMKTDj8UNMXsy+J 15 | 8uiaqEu5wZSVglIHtTvpHkB9YXtzo6/UlgWty6fe68IRwXgZhaM7X/0c 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /examples/server/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMRYfrITXhrmYO 3 | AXtpPF1oOQfjaOwQwI7xvZxEZ9StT55JGT1YIxpIv/M+GIGrtxZgpmpr0wv+bIdA 4 | IVfwVWfysu5QIO1v5nh83B1xwdCaJmZDPqdMp2fLP1u+cbJOPlA2fF46azGX/08j 5 | faqgz/fCtFLkJB/bY2AqQ8YSj+cpcaR3/Mhnznpz5lcEweenNRCWiDIlKlL/kdTq 6 | P3sakqBXH8RN5YsvBuMRBU5ZuKXu5GfZiF8NV0P0bmq7iObIohbu/4yrw7xxwVwm 7 | CIITPeLobPnRh5SKbR1FdAD+iv2LH9vZ6KU22f2rkMIU/YSRoF7GlxwFbRItONlO 8 | X6I7LJEDAgMBAAECggEADoy1Ta4j5FtDsaOxYqGGFbJaOXxztA3DILzcsJKkt2OA 9 | ZryBfhGiAaSKctXUBqMX2PKBigSSSiD40TyOwvOSX9sW1mZCA0JrOpmDD2M6tIAf 10 | sJJ54B/caMGuizYV+TS/CeJ547dW5Piubly5FpM5loi5jr5z9nBxnREOvqu5T15E 11 | MoQ9yhQtd6xJfOHPbMhyqAQeW2RwEPfzwRNWm0fH7kmQSnNgzSfBSG7R5SHXFViT 12 | v1BzjTXfapqGp6xrGzXdQCetij86GzvMMwjjgkN/Q3GExB9d/pTTAu7abZZ21d7C 13 | QjOj9lA274SS5S2FymIKFsevQ7Lr0QDlSReYZ7vNQQKBgQDyno1mkOlxsJUwTobu 14 | 0d7VrXRNxKohdCHrkyItKRLs/SWadUnc9c3YgWmLgvKlxFFtfs7kY4nbxuVAlGpC 15 | zaOf7naI7c27P7ihdfnu3GgrbkvIlTfa+LxIk98j1zERN2mmMKpoqvXf+pT+QDGm 16 | SsMIN7AqSxkRlpEAjeFZkedhOQKBgQDXiY/U4RjUV7tCuf+By3jrsaUzmEkpkgRh 17 | 72d3stk+wTdxzB9XU0v+C/u3NxbvUdv3gjMR70NkNAKf1OiGKsM19YQYeepb4MOo 18 | +62x+M8NTqb+eNle3TenTFn/yhJi/FETKwDqLVWt1FQJflfhWmXCAKffxg3U8FZW 19 | HsW3Cf/QGwKBgCcW8pNG38XIfJD25fiOacellap9+CdBrcFlyEjcaEc0lh1nrkni 20 | dJLgbt/ibjPVCIWKu8zCWNDHH+KixugSd71pz0FKhy4XGykwd99LNaFhuOYNXJ10 21 | G+nZoUcGAcrTUbtL9fi9KrY2ilDYiOdQ/lFRn5mA1f4mcyBSu68RueTJAoGBAM8v 22 | 5tkFwC5uXw3XaatFAmnejCU6oCmbuSbDUTyY6YgPh9KWGxKAea4tqrwF8r/+empD 23 | 9/+ndaqe7F7j9Njzxk7aQS6eExBa0PphZCiOOcpa7t/zH1C3acYh+OmPP3lzfiPk 24 | 1K5HAfNlBZtSnft8QBDrPHQ5GBa3KOcEEZ7Pt949AoGAQ2Wouhs7pBuRq+6RFQ1F 25 | 8KPNHsq8v8ieGBaNTJ+2AilkkKb4Zad5eS8JLpZqR1uq87BAIAehX3hk1ZR9YIez 26 | n2/uG8Lk6oEdpJBGyHQ7w6ulOyawSaXVqKvMfB7xUmMzf4SEpVXY+2DCg/0YIMvR 27 | YSIlsBWilw9Cu8ybuAEG/C8= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /examples/server/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use xmpp::server::Server; 3 | 4 | #[actix::main] 5 | async fn main() { 6 | env::set_var("RUST_LOG", "actix=trace,xmpp_server=trace"); 7 | env_logger::init(); 8 | 9 | let _ = Server::build().launch().await; 10 | } 11 | -------------------------------------------------------------------------------- /proto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xmpp-proto" 3 | version = "0.1.3" 4 | authors = ["Freyskeyd "] 5 | documentation = "https://docs.rs/xmpp-proto" 6 | edition = "2018" 7 | license = "MPL-2.0" 8 | description = """ 9 | xmpp-rs is an implementation of the Extensible Messaging and Presence Protocol (XMPP). 10 | 11 | Based on tokio-rs and futures-rs. It's goal is to be fully tested and usable. 12 | """ 13 | repository = "https://github.com/Freyskeyd/xmpp-rs" 14 | readme = "../README.md" 15 | keywords = ["xmpp", "tokio", "jabber", "IM", "instant-messaging"] 16 | categories = ["network-programming"] 17 | 18 | [dependencies] 19 | # base64 = "0" 20 | # bytes = "0" 21 | # circular = "0" 22 | # futures = "*" 23 | log = "0" 24 | # native-tls = "0" 25 | # tokio-core = "0" 26 | # tokio-io = "0" 27 | # tokio-tls = "0.1.2" 28 | uuid = { version = "0.4", features = ["v4"] } 29 | # xml-rs = {git = "https://github.com/Freyskeyd/xml-rs"} 30 | # xmpp-derive = {path = "../derive", version = "0" } 31 | # sasl = "0.4.1" 32 | # clippy = {version = "*", optional = true} 33 | 34 | xmpp-credentials = { path = "../credentials" } 35 | jid = "0" 36 | xmpp-xml = { path = "../xml" } 37 | 38 | actix = { version = "0.11.0", default_feature = false } 39 | circular = "*" 40 | derive_builder = "0.9.0" 41 | # xmpp-events = { path = "../events" } 42 | [features] 43 | default = [] 44 | -------------------------------------------------------------------------------- /proto/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod non_stanza; 2 | pub mod ns; 3 | mod packet; 4 | mod stanza; 5 | 6 | use jid::Jid; 7 | 8 | pub use non_stanza::*; 9 | pub use packet::*; 10 | pub use stanza::*; 11 | 12 | use xmpp_xml::Element; 13 | 14 | pub trait NonStanzaTrait {} 15 | 16 | impl ToXmlElement for Element { 17 | type Error = std::io::Error; 18 | 19 | fn to_element(&self) -> Result { 20 | Ok(self.clone()) 21 | } 22 | } 23 | 24 | /// FromXmlElement is used to transform an Element to an object 25 | pub trait FromXmlElement { 26 | type Error; 27 | fn from_element(e: &Element) -> Result 28 | where 29 | Self: Sized; 30 | } 31 | 32 | pub trait ToXmlElement { 33 | type Error; 34 | 35 | fn to_element(&self) -> Result; 36 | } 37 | -------------------------------------------------------------------------------- /proto/src/non_stanza.rs: -------------------------------------------------------------------------------- 1 | use xmpp_xml::Element; 2 | 3 | mod auth; 4 | mod bind; 5 | mod close_stream; 6 | mod open_stream; 7 | mod proceed_tls; 8 | mod sasl_success; 9 | mod start_tls; 10 | mod stream_error; 11 | mod stream_features; 12 | 13 | pub use auth::*; 14 | pub use bind::*; 15 | pub use close_stream::*; 16 | pub use open_stream::*; 17 | pub use proceed_tls::*; 18 | pub use sasl_success::*; 19 | pub use start_tls::*; 20 | pub use stream_error::*; 21 | pub use stream_features::*; 22 | 23 | use crate::ToXmlElement; 24 | 25 | /// Define a sub part of a Packet, a NonStanza is the representation of an XML Stream event. 26 | /// It's used by the system to deal with the communication between entities over a network. 27 | #[derive(Debug, Clone)] 28 | pub enum NonStanza { 29 | // TODO: Rename to Initial stream header? 30 | OpenStream(OpenStream), 31 | ProceedTls(ProceedTls), 32 | StartTls(StartTls), 33 | SASLSuccess(SASLSuccess), 34 | StreamFeatures(StreamFeatures), 35 | Auth(Auth), 36 | Bind(Bind), 37 | StreamError(StreamError), 38 | CloseStream(CloseStream), 39 | } 40 | 41 | impl ToXmlElement for NonStanza { 42 | type Error = std::io::Error; 43 | 44 | fn to_element(&self) -> Result { 45 | match self { 46 | NonStanza::Auth(_) => Err(std::io::Error::new(std::io::ErrorKind::Other, "shouldn't be sent back")), 47 | NonStanza::Bind(s) => s.to_element(), 48 | NonStanza::OpenStream(s) => s.to_element(), 49 | NonStanza::ProceedTls(s) => s.to_element(), 50 | NonStanza::SASLSuccess(s) => s.to_element(), 51 | NonStanza::StartTls(_) => Err(std::io::Error::new(std::io::ErrorKind::Other, "shouldn't be sent back")), 52 | NonStanza::StreamFeatures(s) => s.to_element(), 53 | NonStanza::StreamError(s) => s.to_element(), 54 | NonStanza::CloseStream(s) => s.to_element(), 55 | } 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | 63 | #[test] 64 | fn to_element() { 65 | assert!(matches!(NonStanza::OpenStream(OpenStream::default()).to_element(), Ok(_))); 66 | assert!(matches!(NonStanza::StreamFeatures(StreamFeatures::default()).to_element(), Ok(_))); 67 | assert!(matches!(NonStanza::StartTls(StartTls::default()).to_element(), Err(_))); 68 | assert!(matches!(NonStanza::ProceedTls(ProceedTls::default()).to_element(), Ok(_))); 69 | assert!(matches!(NonStanza::SASLSuccess(SASLSuccess::default()).to_element(), Ok(_))); 70 | assert!(matches!(NonStanza::Auth(Auth::default()).to_element(), Err(_))); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /proto/src/non_stanza/auth.rs: -------------------------------------------------------------------------------- 1 | use xmpp_xml::Element; 2 | 3 | use crate::{FromXmlElement, NonStanza, Packet}; 4 | 5 | #[derive(Default, Debug, Clone)] 6 | pub struct Auth { 7 | mechanism: Option, 8 | challenge: Option, 9 | } 10 | 11 | impl Auth { 12 | /// Get a reference to the auth's challenge. 13 | pub fn challenge(&self) -> &Option { 14 | &self.challenge 15 | } 16 | 17 | /// Get a reference to the auth's mechanism. 18 | pub fn mechanism(&self) -> Option<&str> { 19 | self.mechanism.as_ref().map(|v| v.as_ref()) 20 | } 21 | } 22 | 23 | impl From for Packet { 24 | fn from(s: Auth) -> Self { 25 | NonStanza::Auth(s).into() 26 | } 27 | } 28 | 29 | impl FromXmlElement for Auth { 30 | type Error = std::io::Error; 31 | fn from_element(e: &Element) -> Result { 32 | let p = Self { 33 | mechanism: e.get_attr("mechanism").map(|mechanism| mechanism.to_string()), 34 | challenge: Some(e.text().to_string()), 35 | }; 36 | 37 | Ok(p) 38 | } 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use std::io::Write; 44 | 45 | use circular::Buffer; 46 | use xmpp_xml::xml::{reader::XmlEvent, ParserConfig}; 47 | 48 | use super::*; 49 | 50 | const EXPECTED_AUTH: &'static str = "AGp1bGlldAByMG0zMG15cjBtMzA="; 51 | 52 | #[test] 53 | fn parse_proceed_plain() { 54 | let element = Element::from_reader(EXPECTED_AUTH.as_bytes()).unwrap(); 55 | let proceed = Auth::from_element(&element).unwrap(); 56 | 57 | assert_eq!(proceed.mechanism.unwrap(), "PLAIN"); 58 | assert_eq!(proceed.challenge.unwrap(), "AGp1bGlldAByMG0zMG15cjBtMzA="); 59 | } 60 | 61 | #[test] 62 | fn from() { 63 | let mut cfg = ParserConfig::new().whitespace_to_characters(true); 64 | cfg.ignore_end_of_stream = true; 65 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096)); 66 | 67 | reader.source_mut().write(EXPECTED_AUTH.as_bytes()).unwrap(); 68 | let _ = reader.next().unwrap(); 69 | let x = reader.next().unwrap(); 70 | 71 | assert!(matches!(x, XmlEvent::StartElement { .. })); 72 | 73 | if let XmlEvent::StartElement { name, attributes, namespace } = x { 74 | let packet = Packet::parse(&mut reader, name, namespace, attributes); 75 | assert!( 76 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::Auth(_))), 77 | "Packet wasn't an Auth, it was: {:?}", 78 | packet 79 | ); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /proto/src/non_stanza/bind.rs: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | use xmpp_xml::Element; 3 | 4 | use crate::{ns, FromXmlElement, NonStanza, Packet, ToXmlElement}; 5 | 6 | #[derive(Default, Debug, Clone)] 7 | pub struct Bind { 8 | pub resource: Option, 9 | } 10 | 11 | impl From for Packet { 12 | fn from(s: Bind) -> Self { 13 | NonStanza::Bind(s).into() 14 | } 15 | } 16 | 17 | impl FromXmlElement for Bind { 18 | type Error = std::io::Error; 19 | fn from_element(e: &Element) -> Result { 20 | let p = Self { 21 | resource: e.find((ns::BIND, "resource")).map(|e| e.text().to_owned()), 22 | }; 23 | 24 | Ok(p) 25 | } 26 | } 27 | 28 | impl ToXmlElement for Bind { 29 | type Error = std::io::Error; 30 | fn to_element(&self) -> Result { 31 | let mut bind = Element::new((ns::BIND, "bind")); 32 | bind.append_new_child((ns::BIND, "jid")) 33 | .set_text(format!("SOME@localhost/{}", self.resource.clone().unwrap_or_else(|| Uuid::new_v4().to_string()))); 34 | 35 | Ok(bind) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /proto/src/non_stanza/close_stream.rs: -------------------------------------------------------------------------------- 1 | use crate::Packet; 2 | use crate::ToXmlElement; 3 | use crate::{ns, NonStanza}; 4 | use xmpp_xml::Element; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct CloseStream {} 8 | 9 | impl From for Packet { 10 | fn from(s: CloseStream) -> Self { 11 | NonStanza::CloseStream(s).into() 12 | } 13 | } 14 | 15 | impl ToXmlElement for CloseStream { 16 | type Error = std::io::Error; 17 | fn to_element(&self) -> Result { 18 | let root = Element::new((ns::STREAM, "stream")); 19 | 20 | Ok(root) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /proto/src/non_stanza/open_stream.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::{ns, NonStanza, Packet, PacketParsingError, ToXmlElement}; 4 | use jid::Jid; 5 | use uuid::Uuid; 6 | use xmpp_xml::{xml::attribute::OwnedAttribute, Element, QName}; 7 | 8 | /// Define an OpenStream NonStanza packet. 9 | /// 10 | /// Mostly used when negociating connection between parties. 11 | #[derive(derive_builder::Builder, Default, Debug, Clone)] 12 | #[builder(setter(into))] 13 | pub struct OpenStream { 14 | /// An Id generated by the server. 15 | /// 16 | /// The 'id' attribute specifies a unique identifier for the stream, called a "stream ID". The stream ID MUST be generated by the receiving entity when it sends a response stream header and MUST BE unique within the receiving application (normally a server). 17 | /// 18 | /// See [RFC-6120]( https://xmpp.org/rfcs/rfc6120.html#streams-attr-id ) 19 | #[builder(setter(into), default)] 20 | pub id: Option, 21 | /// The 'xml:lang' attribute specifies an entity's preferred or default language for any human-readable XML character data to be sent over the stream 22 | /// 23 | /// See [RFC-6120]( https://xmpp.org/rfcs/rfc6120.html#streams-attr-xmllang ) 24 | #[builder(setter(into), default = "String::from(\"en\")")] 25 | pub lang: String, 26 | /// The inclusion of the version attribute set to a value of at least "1.0" signals support for the stream-related protocols defined in this specification, including TLS negotiation, SASL negotiation, stream features, and stream errors. 27 | /// 28 | /// See [RFC-6120]( https://xmpp.org/rfcs/rfc6120.html#streams-attr-version ) 29 | pub version: String, 30 | #[builder(setter(into), default)] 31 | pub to: Option, 32 | #[builder(setter(into), default)] 33 | pub from: Option, 34 | } 35 | 36 | impl OpenStream { 37 | pub fn from_start_element(attributes: Vec) -> Result { 38 | attributes 39 | .into_iter() 40 | .fold(&mut OpenStreamBuilder::default(), |packet, attribute| match attribute.name.local_name.as_ref() { 41 | "to" if attribute.name.namespace.is_none() => packet.to(Jid::from_str(&attribute.value).ok()), 42 | "from" if attribute.name.namespace.is_none() => packet.from(Jid::from_str(&attribute.value).ok()), 43 | "lang" if attribute.name.namespace == Some(ns::XML_URI.to_string()) => packet.lang(attribute.value), 44 | "version" if attribute.name.namespace.is_none() => packet.version(attribute.value), 45 | "id" if attribute.name.namespace.is_none() => packet.id(attribute.value), 46 | _ => packet, 47 | }) 48 | .build() 49 | .or(Err(PacketParsingError::Unknown)) 50 | } 51 | } 52 | 53 | impl From for Packet { 54 | fn from(s: OpenStream) -> Self { 55 | NonStanza::OpenStream(s).into() 56 | } 57 | } 58 | 59 | impl ToXmlElement for OpenStream { 60 | type Error = std::io::Error; 61 | fn to_element(&self) -> Result { 62 | let mut element = Element::new((ns::CLIENT, "stream")).write_end_tag(false); 63 | 64 | let _ = element.set_namespace_prefix(ns::STREAM, "stream"); 65 | 66 | let qname_stream = QName::from_ns_name(Some(ns::STREAM), "stream"); 67 | element 68 | .set_tag(&qname_stream) 69 | .set_attr("version", self.version.to_string()) 70 | .set_attr((ns::XML_URI, "lang"), self.lang.to_string()) 71 | .set_attr("id", self.id.clone().unwrap_or_else(|| Uuid::new_v4().to_string())); 72 | 73 | if let Some(ref from) = self.from { 74 | element.set_attr("from", from.to_string()); 75 | } 76 | 77 | if let Some(ref to) = self.to { 78 | element.set_attr("to", to.to_string()); 79 | } 80 | 81 | Ok(element) 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use super::*; 88 | 89 | use circular::Buffer; 90 | use std::io::Write; 91 | 92 | use xmpp_xml::xml::{reader::XmlEvent, ParserConfig}; 93 | 94 | #[test] 95 | fn from() { 96 | let initial_stream = ""; 97 | 98 | let mut cfg = ParserConfig::new().whitespace_to_characters(true); 99 | cfg.ignore_end_of_stream = true; 100 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096)); 101 | 102 | reader.source_mut().write(initial_stream.as_bytes()).unwrap(); 103 | let _ = reader.next().unwrap(); 104 | let x = reader.next().unwrap(); 105 | 106 | assert!(matches!(x, XmlEvent::StartElement { .. })); 107 | 108 | if let XmlEvent::StartElement { name, attributes, namespace } = x { 109 | let packet = Packet::parse(&mut reader, name, namespace, attributes); 110 | assert!( 111 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::OpenStream(_))), 112 | "Packet wasn't an OpenStream, it was: {:?}", 113 | packet 114 | ); 115 | } 116 | } 117 | 118 | #[test] 119 | fn to_element() { 120 | let id = Uuid::new_v4().to_string(); 121 | let lang: String = "en".into(); 122 | let version: String = "1.0".into(); 123 | let to: Jid = Jid::from_str("jid@localhost").unwrap(); 124 | let from: Jid = Jid::from_str("localhost").unwrap(); 125 | let expected = format!( 126 | r#""#, 127 | id = id, 128 | lang = lang, 129 | from = from, 130 | to = to, 131 | version = version 132 | ); 133 | 134 | let open_stream = OpenStreamBuilder::default().id(id).lang(lang).version(version).to(to).from(from).build(); 135 | 136 | assert!(open_stream.is_ok()); 137 | 138 | assert!(matches!(open_stream, Ok(OpenStream { .. }))); 139 | 140 | let mut output: Vec = Vec::new(); 141 | let _ = open_stream.unwrap().to_element().unwrap().to_writer(&mut output); 142 | 143 | let generated = String::from_utf8(output).unwrap(); 144 | 145 | assert!(expected == generated, "{} != {}", expected, generated); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /proto/src/non_stanza/proceed_tls.rs: -------------------------------------------------------------------------------- 1 | use xmpp_xml::Element; 2 | 3 | use crate::{ns, FromXmlElement, NonStanza, Packet, ToXmlElement}; 4 | 5 | #[derive(Default, Debug, Clone)] 6 | pub struct ProceedTls { 7 | mechanism: Option, 8 | challenge: Option, 9 | } 10 | 11 | impl From for Packet { 12 | fn from(s: ProceedTls) -> Self { 13 | NonStanza::ProceedTls(s).into() 14 | } 15 | } 16 | 17 | impl ToXmlElement for ProceedTls { 18 | type Error = std::io::Error; 19 | fn to_element(&self) -> Result { 20 | let root = Element::new((ns::TLS, "proceed")); 21 | 22 | Ok(root) 23 | } 24 | } 25 | 26 | impl FromXmlElement for ProceedTls { 27 | type Error = std::io::Error; 28 | fn from_element(e: &Element) -> Result { 29 | let p = Self { 30 | mechanism: e.get_attr("mechanism").map(|mechanism| mechanism.to_string()), 31 | challenge: Some(e.text().to_string()), 32 | }; 33 | 34 | Ok(p) 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use std::io::Write; 41 | 42 | use circular::Buffer; 43 | use xmpp_xml::{ 44 | xml::{reader::XmlEvent, ParserConfig}, 45 | WriteOptions, 46 | }; 47 | 48 | use super::*; 49 | 50 | const EXPECTED_PROCEEDTLS: &'static str = r#""#; 51 | 52 | #[test] 53 | fn to_element() { 54 | let proceed = ProceedTls::default(); 55 | 56 | let mut output: Vec = Vec::new(); 57 | let _ = proceed.to_element().unwrap().to_writer_with_options(&mut output, WriteOptions::new().set_xml_prolog(None)); 58 | 59 | let generated = String::from_utf8(output).unwrap(); 60 | 61 | assert!(EXPECTED_PROCEEDTLS == generated); 62 | } 63 | 64 | #[test] 65 | fn from() { 66 | let mut cfg = ParserConfig::new().whitespace_to_characters(true); 67 | cfg.ignore_end_of_stream = true; 68 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096)); 69 | 70 | reader.source_mut().write(EXPECTED_PROCEEDTLS.as_bytes()).unwrap(); 71 | let _ = reader.next().unwrap(); 72 | let x = reader.next().unwrap(); 73 | 74 | assert!(matches!(x, XmlEvent::StartElement { .. })); 75 | 76 | if let XmlEvent::StartElement { name, attributes, namespace } = x { 77 | let packet = Packet::parse(&mut reader, name, namespace, attributes); 78 | assert!( 79 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::ProceedTls(_))), 80 | "Packet wasn't an ProceedTls, it was: {:?}", 81 | packet 82 | ); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /proto/src/non_stanza/sasl_success.rs: -------------------------------------------------------------------------------- 1 | use xmpp_xml::Element; 2 | 3 | use crate::{ns, NonStanza, Packet, ToXmlElement}; 4 | 5 | #[derive(Default, Debug, Clone)] 6 | pub struct SASLSuccess {} 7 | 8 | impl From for Packet { 9 | fn from(s: SASLSuccess) -> Self { 10 | NonStanza::SASLSuccess(s).into() 11 | } 12 | } 13 | 14 | impl ToXmlElement for SASLSuccess { 15 | type Error = std::io::Error; 16 | fn to_element(&self) -> Result { 17 | Ok(Element::new((ns::SASL, "success"))) 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use xmpp_xml::WriteOptions; 24 | 25 | use super::*; 26 | 27 | const EXPECTED_SUCCESS: &'static str = r#""#; 28 | 29 | #[test] 30 | fn from() { 31 | let packet: Packet = SASLSuccess::default().into(); 32 | assert!( 33 | matches!(packet, Packet::NonStanza(ref stanza) if matches!(**stanza, NonStanza::SASLSuccess(_))), 34 | "Packet wasn't an SASLSuccess", 35 | ); 36 | } 37 | 38 | #[test] 39 | fn to() { 40 | let mut output: Vec = Vec::new(); 41 | let _ = SASLSuccess::default() 42 | .to_element() 43 | .unwrap() 44 | .to_writer_with_options(&mut output, WriteOptions::new().set_xml_prolog(None)); 45 | 46 | let generated = String::from_utf8(output).unwrap(); 47 | 48 | assert!(EXPECTED_SUCCESS == generated); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /proto/src/non_stanza/start_tls.rs: -------------------------------------------------------------------------------- 1 | use xmpp_xml::Element; 2 | 3 | use crate::{FromXmlElement, NonStanza, Packet, PacketParsingError}; 4 | 5 | #[derive(Debug, Default, Clone)] 6 | pub struct StartTls {} 7 | 8 | impl From for Packet { 9 | fn from(s: StartTls) -> Self { 10 | NonStanza::StartTls(s).into() 11 | } 12 | } 13 | 14 | impl FromXmlElement for StartTls { 15 | type Error = PacketParsingError; 16 | 17 | fn from_element(_: &Element) -> Result 18 | where 19 | Self: Sized, 20 | { 21 | Ok(Self {}) 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use std::io::Write; 28 | 29 | use circular::Buffer; 30 | use xmpp_xml::xml::{reader::XmlEvent, ParserConfig}; 31 | 32 | use super::*; 33 | 34 | const EXPECTED_STARTTLS: &'static str = ""; 35 | 36 | #[test] 37 | fn from() { 38 | let mut cfg = ParserConfig::new().whitespace_to_characters(true); 39 | cfg.ignore_end_of_stream = true; 40 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096)); 41 | 42 | reader.source_mut().write(EXPECTED_STARTTLS.as_bytes()).unwrap(); 43 | let _ = reader.next().unwrap(); 44 | let x = reader.next().unwrap(); 45 | 46 | assert!(matches!(x, XmlEvent::StartElement { .. })); 47 | 48 | if let XmlEvent::StartElement { name, attributes, namespace } = x { 49 | let packet = Packet::parse(&mut reader, name, namespace, attributes); 50 | assert!( 51 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::StartTls(_))), 52 | "Packet wasn't an StartTls, it was: {:?}", 53 | packet 54 | ); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /proto/src/non_stanza/stream_error.rs: -------------------------------------------------------------------------------- 1 | use crate::Packet; 2 | use crate::ToXmlElement; 3 | use crate::{ns, NonStanza}; 4 | use xmpp_xml::Element; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct StreamError { 8 | pub kind: StreamErrorKind, 9 | //TODO: Implement error text 10 | // pub text: String 11 | } 12 | 13 | #[derive(Debug, Clone)] 14 | pub enum StreamErrorKind { 15 | BadNamespacePrefix, 16 | HostUnknown, 17 | InvalidNamespace, 18 | NotAuthorized, 19 | UnsupportedEncoding, 20 | UnsupportedVersion, 21 | } 22 | 23 | impl From for Packet { 24 | fn from(s: StreamError) -> Self { 25 | NonStanza::StreamError(s).into() 26 | } 27 | } 28 | 29 | impl ToXmlElement for StreamError { 30 | type Error = std::io::Error; 31 | fn to_element(&self) -> Result { 32 | let mut root = Element::new((ns::STREAM, "error")); 33 | 34 | match self.kind { 35 | StreamErrorKind::BadNamespacePrefix => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "bad-namespace-prefix")), 36 | StreamErrorKind::HostUnknown => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "host-unknown")), 37 | StreamErrorKind::InvalidNamespace => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "invalid-namespace")), 38 | StreamErrorKind::NotAuthorized => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "not-authorized")), 39 | StreamErrorKind::UnsupportedEncoding => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "unsupported-encoding")), 40 | StreamErrorKind::UnsupportedVersion => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "unsupported-version")), 41 | }; 42 | 43 | Ok(root) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /proto/src/non_stanza/stream_features.rs: -------------------------------------------------------------------------------- 1 | use xmpp_xml::Element; 2 | 3 | use crate::{ns, FromXmlElement, NonStanza, Packet, ToXmlElement}; 4 | 5 | #[derive(derive_builder::Builder, Default, Debug, Clone)] 6 | #[builder(setter(into))] 7 | pub struct StreamFeatures { 8 | pub features: Features, 9 | } 10 | 11 | impl FromXmlElement for StreamFeatures { 12 | type Error = std::io::Error; 13 | 14 | fn from_element(e: &Element) -> Result 15 | where 16 | Self: Sized, 17 | { 18 | Ok(Self { 19 | features: e 20 | .children() 21 | .map(|child| match Features::from(child.tag().name()) { 22 | Features::Mechanisms(_) => Features::Mechanisms(child.children().map(|m| m.text().to_string()).collect()), 23 | feature => feature, 24 | }) 25 | .collect::>() 26 | .first() 27 | .cloned() 28 | .unwrap_or(Features::Unknown), 29 | }) 30 | } 31 | } 32 | 33 | impl From for Packet { 34 | fn from(s: StreamFeatures) -> Self { 35 | NonStanza::StreamFeatures(s).into() 36 | } 37 | } 38 | 39 | impl ToXmlElement for StreamFeatures { 40 | type Error = std::io::Error; 41 | fn to_element(&self) -> Result { 42 | let mut root = Element::new("stream:features"); 43 | 44 | match self.features { 45 | Features::StartTls => { 46 | let starttls = root.append_new_child((ns::TLS, "starttls")); 47 | starttls.append_new_child((ns::TLS, "required")); 48 | } 49 | Features::Bind => { 50 | root.append_new_child((ns::BIND, "bind")); 51 | } 52 | Features::Mechanisms(ref mechanisms) => { 53 | let node = root.append_new_child((ns::SASL, "mechanisms")); 54 | mechanisms.iter().for_each(|mech| { 55 | node.append_new_child((ns::SASL, "mechanism")).set_text(mech); 56 | }); 57 | } 58 | Features::Unknown => {} 59 | } 60 | 61 | Ok(root) 62 | } 63 | } 64 | 65 | #[derive(Debug, Clone, PartialEq)] 66 | pub enum Features { 67 | StartTls, 68 | Bind, 69 | Mechanisms(Vec), 70 | Unknown, 71 | } 72 | 73 | impl Default for Features { 74 | fn default() -> Self { 75 | Self::Unknown 76 | } 77 | } 78 | 79 | impl From<&str> for Features { 80 | fn from(e: &str) -> Self { 81 | match e { 82 | "starttls" => Features::StartTls, 83 | "bind" => Features::Bind, 84 | "mechanisms" => Features::Mechanisms(vec![]), 85 | _ => Features::Unknown, 86 | } 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | 94 | use circular::Buffer; 95 | use std::io::Write; 96 | 97 | use xmpp_xml::{ 98 | xml::{reader::XmlEvent, ParserConfig}, 99 | WriteOptions, 100 | }; 101 | 102 | const INITIAL_STREAM: &'static str = ""; 103 | const EXPECTED_STARTTLS: &'static str = r#""#; 104 | const EXPECTED_BIND: &'static str = r#""#; 105 | const EXPECTED_MECHANISMS: &'static str = r#"EXTERNALSCRAM-SHA-1-PLUSSCRAM-SHA-1PLAIN"#; 106 | 107 | #[test] 108 | fn parse_starttls() { 109 | let stream_feature = StreamFeaturesBuilder::default().features(Features::StartTls).build(); 110 | 111 | let mut output: Vec = Vec::new(); 112 | let _ = stream_feature 113 | .unwrap() 114 | .to_element() 115 | .unwrap() 116 | .to_writer_with_options(&mut output, WriteOptions::new().set_xml_prolog(None)); 117 | 118 | let generated = String::from_utf8(output).unwrap(); 119 | 120 | assert!(EXPECTED_STARTTLS == generated); 121 | 122 | let mut cfg = ParserConfig::new().whitespace_to_characters(true); 123 | cfg.ignore_end_of_stream = true; 124 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096)); 125 | 126 | reader.source_mut().write(INITIAL_STREAM.as_bytes()).unwrap(); 127 | reader.source_mut().write(EXPECTED_STARTTLS.as_bytes()).unwrap(); 128 | 129 | let _ = reader.next().unwrap(); 130 | let _ = reader.next().unwrap(); 131 | let x = reader.next().unwrap(); 132 | 133 | assert!(matches!(x, XmlEvent::StartElement { .. })); 134 | 135 | if let XmlEvent::StartElement { name, attributes, namespace } = x { 136 | let packet = Packet::parse(&mut reader, name, namespace, attributes); 137 | assert!( 138 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::StreamFeatures(StreamFeatures { features: Features::StartTls }))), 139 | "Packet wasn't an StreamFeatures::StartTls, it was: {:?}", 140 | packet 141 | ); 142 | } 143 | } 144 | 145 | #[test] 146 | fn parse_bind() { 147 | let stream_feature = StreamFeaturesBuilder::default().features(Features::Bind).build(); 148 | 149 | let mut output: Vec = Vec::new(); 150 | let _ = stream_feature 151 | .unwrap() 152 | .to_element() 153 | .unwrap() 154 | .to_writer_with_options(&mut output, WriteOptions::new().set_xml_prolog(None)); 155 | 156 | let generated = String::from_utf8(output).unwrap(); 157 | 158 | assert!(EXPECTED_BIND == generated); 159 | 160 | let mut cfg = ParserConfig::new().whitespace_to_characters(true); 161 | cfg.ignore_end_of_stream = true; 162 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096)); 163 | 164 | reader.source_mut().write(INITIAL_STREAM.as_bytes()).unwrap(); 165 | reader.source_mut().write(EXPECTED_BIND.as_bytes()).unwrap(); 166 | 167 | let _ = reader.next().unwrap(); 168 | let _ = reader.next().unwrap(); 169 | let x = reader.next().unwrap(); 170 | 171 | assert!(matches!(x, XmlEvent::StartElement { .. })); 172 | 173 | if let XmlEvent::StartElement { name, attributes, namespace } = x { 174 | let packet = Packet::parse(&mut reader, name, namespace, attributes); 175 | assert!( 176 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::StreamFeatures(StreamFeatures { features: Features::Bind }))), 177 | "Packet wasn't an StreamFeatures::Bind, it was: {:?}", 178 | packet 179 | ); 180 | } 181 | } 182 | 183 | #[test] 184 | fn parse_mechanisms() { 185 | let mechs: Vec = "EXTERNAL SCRAM-SHA-1-PLUS SCRAM-SHA-1 PLAIN".split_whitespace().map(String::from).collect(); 186 | let stream_feature = StreamFeaturesBuilder::default().features(Features::Mechanisms(mechs.clone())).build(); 187 | 188 | let mut output: Vec = Vec::new(); 189 | let _ = stream_feature 190 | .unwrap() 191 | .to_element() 192 | .unwrap() 193 | .to_writer_with_options(&mut output, WriteOptions::new().set_xml_prolog(None)); 194 | 195 | let generated = String::from_utf8(output).unwrap(); 196 | 197 | assert!(EXPECTED_MECHANISMS == generated); 198 | 199 | let mut cfg = ParserConfig::new().whitespace_to_characters(true); 200 | cfg.ignore_end_of_stream = true; 201 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096)); 202 | 203 | reader.source_mut().write(INITIAL_STREAM.as_bytes()).unwrap(); 204 | reader.source_mut().write(EXPECTED_MECHANISMS.as_bytes()).unwrap(); 205 | 206 | let _ = reader.next().unwrap(); 207 | let _ = reader.next().unwrap(); 208 | let x = reader.next().unwrap(); 209 | 210 | assert!(matches!(x, XmlEvent::StartElement { .. })); 211 | 212 | if let XmlEvent::StartElement { name, attributes, namespace } = x { 213 | let packet = Packet::parse(&mut reader, name, namespace, attributes); 214 | assert!( 215 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::StreamFeatures(StreamFeatures { features: Features::Mechanisms(ref m) }) if m == &mechs)), 216 | "Packet wasn't an StreamFeatures::Mechanisms, it was: {:?}", 217 | packet 218 | ); 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /proto/src/ns.rs: -------------------------------------------------------------------------------- 1 | pub const XML_URI: &str = "http://www.w3.org/XML/1998/namespace"; 2 | pub const CLIENT: &str = "jabber:client"; 3 | pub const SERVER: &str = "jabber:server"; 4 | pub const STREAM: &str = "http://etherx.jabber.org/streams"; 5 | pub const TLS: &str = "urn:ietf:params:xml:ns:xmpp-tls"; 6 | pub const SASL: &str = "urn:ietf:params:xml:ns:xmpp-sasl"; 7 | pub const BIND: &str = "urn:ietf:params:xml:ns:xmpp-bind"; 8 | pub const SESSION: &str = "urn:ietf:params:xml:ns:xmpp-session"; 9 | pub const STANZAS: &str = "urn:ietf:params:xml:ns:xmpp-stanzas"; 10 | pub const PING: &str = "urn:xmpp:ping"; 11 | -------------------------------------------------------------------------------- /proto/src/packet.rs: -------------------------------------------------------------------------------- 1 | use crate::{non_stanza::StartTls, ns, stanza::GenericIq, Auth, OpenStream, StreamErrorKind, StreamFeatures, ToXmlElement}; 2 | use crate::{FromXmlElement, ProceedTls}; 3 | use crate::{NonStanza, NonStanzaTrait, Stanza}; 4 | 5 | use actix::Message; 6 | use circular::Buffer; 7 | use log::error; 8 | use std::{ 9 | convert::{TryFrom, TryInto}, 10 | io::{ErrorKind, Write}, 11 | }; 12 | use xmpp_xml::WriteOptions; 13 | use xmpp_xml::{ 14 | xml::{attribute::OwnedAttribute, name::OwnedName, namespace::Namespace, EventReader}, 15 | Element, 16 | }; 17 | 18 | #[derive(Debug, Message, Clone)] 19 | #[rtype(result = "Result, ()>")] 20 | pub enum Packet { 21 | /// Represent a packet which is an XML Stream 22 | NonStanza(Box), 23 | /// Represent a packet which isn't an XML Stanza 24 | Stanza(Box), 25 | InvalidPacket(Box), 26 | } 27 | 28 | impl From for Packet 29 | where 30 | T: NonStanzaTrait, 31 | { 32 | fn from(s: T) -> Self { 33 | s.into() 34 | } 35 | } 36 | 37 | impl From for Packet { 38 | fn from(s: NonStanza) -> Self { 39 | Packet::NonStanza(Box::new(s)) 40 | } 41 | } 42 | 43 | impl From for Packet { 44 | fn from(s: Stanza) -> Self { 45 | Packet::Stanza(Box::new(s)) 46 | } 47 | } 48 | 49 | #[derive(Debug)] 50 | pub enum PacketParsingError { 51 | Final, 52 | Xml(xmpp_xml::Error), 53 | Io, 54 | Unknown, 55 | InvalidNamespace, 56 | } 57 | 58 | impl From for PacketParsingError { 59 | fn from(e: xmpp_xml::Error) -> Self { 60 | PacketParsingError::Xml(e) 61 | } 62 | } 63 | 64 | impl From for PacketParsingError { 65 | fn from(_: std::io::Error) -> Self { 66 | PacketParsingError::Io 67 | } 68 | } 69 | 70 | impl TryFrom for Packet { 71 | type Error = PacketParsingError; 72 | 73 | fn try_from(element: Element) -> Result { 74 | match (element.tag().ns(), element.tag().name()) { 75 | (Some(ns::STREAM), "features") => Ok(StreamFeatures::from_element(&element)?.into()), 76 | (Some(ns::SASL), "auth") => Ok(Auth::from_element(&element)?.into()), 77 | (Some(ns::TLS), "starttls") => Ok(StartTls::from_element(&element)?.into()), 78 | (Some(ns::TLS), "proceed") => Ok(ProceedTls::from_element(&element)?.into()), 79 | (Some(ns::CLIENT), "iq") => Ok(GenericIq::from_element(&element)?.into()), 80 | (Some(ns::CLIENT), "message") => Ok(Stanza::Message(element).into()), 81 | (Some(ns::CLIENT), "presence") => Ok(Stanza::Presence(element).into()), 82 | (_, name) if ["features", "auth", "starttls", "proceed", "iq", "message", "presence"].contains(&name) => Ok(Packet::InvalidPacket(Box::new(StreamErrorKind::BadNamespacePrefix))), 83 | e => { 84 | error!("{:?}", e); 85 | Err(PacketParsingError::Unknown) 86 | } 87 | } 88 | } 89 | } 90 | 91 | impl Packet { 92 | pub fn write_to_stream(&self, mut stream: W) -> Result<(), std::io::Error> { 93 | match self { 94 | Packet::NonStanza(s) if matches!(**s, NonStanza::CloseStream(_)) => { 95 | stream.write_all(b"")?; 96 | Ok(()) 97 | } 98 | Packet::NonStanza(s) if matches!(**s, NonStanza::OpenStream(_)) => Ok(s.to_element()?.to_writer(stream)?), 99 | Packet::NonStanza(s) => Ok(s.to_element()?.to_writer_with_options(stream, WriteOptions::new().set_xml_prolog(None))?), 100 | Packet::Stanza(s) => Ok(s.to_element()?.to_writer_with_options(stream, WriteOptions::new().set_xml_prolog(None))?), 101 | // TODO: should we fail the write or ignore it ? 102 | Packet::InvalidPacket(_) => Err(std::io::Error::new(ErrorKind::InvalidData, "Invalid packet shouldn't be written back")), 103 | } 104 | } 105 | 106 | pub fn parse(buffer: &mut EventReader, name: OwnedName, namespace: Namespace, attributes: Vec) -> Result { 107 | match (name.namespace_ref(), name.prefix_ref(), name.local_name.as_ref()) { 108 | // open stream isn't an element 109 | (Some(ns::STREAM), Some("stream"), "stream") => Ok(OpenStream::from_start_element(attributes)?.into()), 110 | (_, Some("stream"), "stream") => Ok(Packet::InvalidPacket(Box::new(StreamErrorKind::InvalidNamespace))), 111 | (_, _, "stream") => Ok(Packet::InvalidPacket(Box::new(StreamErrorKind::BadNamespacePrefix))), 112 | _ => Element::from_start_element(name, attributes, namespace, None, buffer)?.try_into(), 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /proto/src/stanza.rs: -------------------------------------------------------------------------------- 1 | use xmpp_xml::Element; 2 | 3 | use crate::ToXmlElement; 4 | 5 | pub use self::generic_iq::*; 6 | 7 | mod generic_iq; 8 | 9 | /// Define a sub part of a Packet, a Stanza is the representation of an Xmpp Stanza which can be a 10 | /// Presence, an IQ or a Message. 11 | #[derive(Debug, Clone)] 12 | pub enum Stanza { 13 | IQ(GenericIq), 14 | Message(Element), 15 | Presence(Element), 16 | } 17 | 18 | impl ToXmlElement for Stanza { 19 | type Error = std::io::Error; 20 | 21 | fn to_element(&self) -> Result { 22 | match self { 23 | Stanza::IQ(iq) => iq.to_element(), 24 | Stanza::Message(s) => s.to_element(), 25 | Stanza::Presence(s) => s.to_element(), 26 | } 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use uuid::Uuid; 33 | 34 | use crate::FromXmlElement; 35 | 36 | use super::*; 37 | 38 | #[test] 39 | fn to_element() { 40 | let mut element = Element::new("iq"); 41 | element.set_attr("id", GenericIq::unique_id()).set_attr("type", "get"); 42 | element.append_child(Element::new("test")); 43 | let iq = GenericIq::from_element(&element).unwrap(); 44 | 45 | assert!(matches!(Stanza::IQ(iq).to_element(), Ok(_))); 46 | assert!(matches!(Stanza::Message(Element::new("message")).to_element(), Ok(_))); 47 | assert!(matches!(Stanza::Presence(Element::new("presence")).to_element(), Ok(_))); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /proto/src/stanza/generic_iq.rs: -------------------------------------------------------------------------------- 1 | use super::Stanza; 2 | use crate::{FromXmlElement, Jid, Packet, ToXmlElement}; 3 | 4 | use std::fmt; 5 | use std::io; 6 | use std::str::FromStr; 7 | use uuid::Uuid; 8 | use xmpp_xml::Element; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct GenericIq { 12 | id: String, 13 | iq_type: IqType, 14 | to: Option, 15 | from: Option, 16 | element: Option, 17 | // error: Option, 18 | } 19 | 20 | impl From for Packet { 21 | fn from(s: GenericIq) -> Self { 22 | Stanza::IQ(s).into() 23 | } 24 | } 25 | impl Default for GenericIq { 26 | fn default() -> Self { 27 | Self::new("", IqType::Get) 28 | } 29 | } 30 | 31 | #[derive(Debug, Copy, Clone, PartialEq)] 32 | pub enum IqType { 33 | Get, 34 | Set, 35 | Result, 36 | Error, 37 | } 38 | 39 | impl From for String { 40 | fn from(iq_type: IqType) -> String { 41 | match iq_type { 42 | IqType::Get => "get".to_string(), 43 | IqType::Set => "set".to_string(), 44 | IqType::Result => "result".to_string(), 45 | IqType::Error => "error".to_string(), 46 | } 47 | } 48 | } 49 | 50 | impl GenericIq { 51 | pub fn new(id: &T, iq_type: IqType) -> GenericIq { 52 | GenericIq { 53 | id: id.to_string(), 54 | iq_type, 55 | to: None, 56 | from: None, 57 | element: None, 58 | } 59 | } 60 | 61 | pub fn unique_id() -> String { 62 | Uuid::new_v4().to_string() 63 | } 64 | 65 | pub fn get_element(&self) -> Option<&Element> { 66 | self.element.as_ref() 67 | } 68 | 69 | pub fn set_type(&mut self, iq_type: IqType) -> &mut Self { 70 | self.iq_type = iq_type; 71 | self 72 | } 73 | 74 | pub fn get_type(&self) -> IqType { 75 | self.iq_type 76 | } 77 | 78 | pub fn get_id(&self) -> &str { 79 | self.id.as_ref() 80 | } 81 | 82 | pub fn set_id(&mut self, id: &T) -> &mut Self { 83 | self.id = id.to_string(); 84 | self 85 | } 86 | 87 | pub fn set_to(&mut self, jid: Option) -> &mut Self { 88 | self.to = jid; 89 | 90 | self 91 | } 92 | 93 | pub fn get_to(&self) -> Option<&Jid> { 94 | self.to.as_ref() 95 | } 96 | 97 | pub fn set_from(&mut self, jid: Option) -> &mut Self { 98 | self.from = jid; 99 | 100 | self 101 | } 102 | 103 | pub fn get_from(&self) -> Option<&Jid> { 104 | self.from.as_ref() 105 | } 106 | } 107 | impl ToXmlElement for GenericIq { 108 | type Error = io::Error; 109 | 110 | fn to_element(&self) -> Result { 111 | Ok(self.element.clone().unwrap()) 112 | } 113 | } 114 | 115 | impl FromXmlElement for GenericIq { 116 | type Error = io::Error; 117 | fn from_element(e: &Element) -> Result { 118 | let id = match e.get_attr("id") { 119 | Some(id) => id.to_string(), 120 | None => return Err(io::Error::new(io::ErrorKind::InvalidInput, "ID is required")), 121 | }; 122 | 123 | let iq_type = match e.get_attr("type") { 124 | Some(t) => match IqType::from_str(t) { 125 | Ok(t) => t, 126 | Err(e) => return Err(e), 127 | }, 128 | None => return Err(io::Error::new(io::ErrorKind::InvalidInput, "TYPE is required")), 129 | }; 130 | 131 | // Validation types 132 | match iq_type { 133 | IqType::Result => { 134 | if e.child_count() > 1 { 135 | return Err(io::Error::new(io::ErrorKind::InvalidInput, "An IQ stanza of type \"result\" MUST include zero or one child elements.")); 136 | } 137 | } 138 | 139 | // An error stanza MUST contain an child element 140 | IqType::Error => { 141 | if e.find("{jabber:client}error").is_none() { 142 | return Err(io::Error::new( 143 | io::ErrorKind::InvalidInput, 144 | "An IQ stanza of type \"error\" SHOULD include the child element contained in the associated \"get\" or \"set\" and MUST include an child", 145 | )); 146 | } 147 | } 148 | IqType::Set | IqType::Get => { 149 | if e.child_count() != 1 { 150 | // https://xmpp.org/rfcs/rfc3920.html#stanzas 151 | return Err(io::Error::new(io::ErrorKind::InvalidInput, "IqType Get/Set MUST contain one and only one child")); 152 | } 153 | } 154 | } 155 | 156 | // An child MUST NOT be included if the 'type' attribute has a value other than "error" 157 | match iq_type { 158 | IqType::Set | IqType::Get | IqType::Result => { 159 | if e.find("error").is_some() { 160 | return Err(io::Error::new( 161 | io::ErrorKind::InvalidInput, 162 | "An child MUST NOT be included if the 'type' attribute has a value other than \"error\"", 163 | )); 164 | } 165 | } 166 | _ => {} 167 | } 168 | 169 | let to = { 170 | if let Some(t) = e.get_attr("to") { 171 | match Jid::from_str(t) { 172 | Ok(j) => Some(j), 173 | Err(_) => None, 174 | } 175 | } else { 176 | None 177 | } 178 | }; 179 | 180 | let from = { 181 | if let Some(f) = e.get_attr("from") { 182 | match Jid::from_str(f) { 183 | Ok(j) => Some(j), 184 | Err(_) => None, 185 | } 186 | } else { 187 | None 188 | } 189 | }; 190 | 191 | // let error = { 192 | // if iq_type == IqType::Error { 193 | // match StanzaError::from_element(e.find("error").unwrap().to_owned()) { 194 | // Ok(error_element) => Some(error_element), 195 | // Err(_) => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Unparsable error element")), 196 | // } 197 | // } else { 198 | // None 199 | // } 200 | // }; 201 | 202 | Ok(GenericIq { 203 | id, 204 | iq_type, 205 | to, 206 | from, 207 | element: Some(e.clone()), 208 | // error, 209 | }) 210 | } 211 | } 212 | 213 | impl FromStr for IqType { 214 | type Err = io::Error; 215 | fn from_str(s: &str) -> Result { 216 | match s.to_lowercase().as_ref() { 217 | "get" => Ok(IqType::Get), 218 | "set" => Ok(IqType::Set), 219 | "result" => Ok(IqType::Result), 220 | "error" => Ok(IqType::Error), 221 | _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "Unsupported IqType")), 222 | } 223 | } 224 | } 225 | impl fmt::Display for IqType { 226 | // This trait requires `fmt` with this exact signature. 227 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 228 | // Write strictly the first element into the supplied output 229 | // stream: `f`. Returns `fmt::Result` which indicates whether the 230 | // operation succeeded or failed. Note that `write!` uses syntax which 231 | // is very similar to `println!`. 232 | write!( 233 | f, 234 | "{}", 235 | match *self { 236 | IqType::Get => "get".to_string(), 237 | IqType::Set => "set".to_string(), 238 | IqType::Result => "result".to_string(), 239 | IqType::Error => "error".to_string(), 240 | } 241 | ) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /proto/src/stanza/message.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freyskeyd/xmpp-rs/3e9545c134d33059099c2f079c459047fdd01918/proto/src/stanza/message.rs -------------------------------------------------------------------------------- /proto/src/stanza/presence.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freyskeyd/xmpp-rs/3e9545c134d33059099c2f079c459047fdd01918/proto/src/stanza/presence.rs -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xmpp-server" 3 | version = "0.1.3" 4 | license = "MPL-2.0" 5 | documentation = "https://docs.rs/xmpp-server" 6 | authors = ["Freyskeyd "] 7 | 8 | edition = "2018" 9 | description = """ 10 | xmpp-rs is an implementation of the Extensible Messaging and Presence Protocol (XMPP). 11 | 12 | Based on tokio-rs and futures-rs. It's goal is to be fully tested and usable. 13 | """ 14 | repository = "https://github.com/Freyskeyd/xmpp-rs" 15 | keywords = ["xmpp", "tokio", "jabber", "IM", "instant-messaging"] 16 | categories = ["network-programming"] 17 | 18 | [dependencies] 19 | actix = { version = "0.12.0" } 20 | actix-codec = "0.4.0-beta.1" 21 | actix-http = { version = "3.0.0-beta.4", default-features = false } 22 | actix-rt = { version = "2.2", default-features = false } 23 | actix-service = "2.0.0-beta.5" 24 | actix-tls = "3.0.0-beta.5" 25 | 26 | # actix = "0.11.0" 27 | # actix-tls = "*" 28 | actix-web = "4.0.0-beta.8" 29 | actix-web-actors = "4.0.0-beta.6" 30 | # actix-web-actors = { git = "https://github.com/actix/actix-web" } 31 | # actix-net = "*" 32 | # actix-server = "*" 33 | # actix-service = "*" 34 | # actix-codec = "*" 35 | byteorder = "*" 36 | futures = "*" 37 | futures-util = "*" 38 | log = "*" 39 | bytes = "1" 40 | # byteorder = "1.3" 41 | serde = "*" 42 | serde_json = "*" 43 | tokio = { version = "1", features = ["full"] } 44 | # # tokio = { version = "0.2" } 45 | tokio-util = { version = "0.6", features = ["codec"] } 46 | tokio-rustls = "*" 47 | uuid = { version = "0.4", features = ["v4"] } 48 | 49 | circular = "*" 50 | xml-rs = "*" 51 | xmpp-xml = { path = "../xml" } 52 | xmpp-proto = { path = "../proto" } 53 | sasl = "*" 54 | 55 | derive_builder = "0.10.0" 56 | 57 | jid = "0" 58 | config = "0.11" 59 | async-trait ="*" 60 | lazy_static = "1" 61 | tower-service = "0.3.1" 62 | base64 = "0.13.0" 63 | 64 | [dev-dependencies] 65 | demonstrate = "0.4.5" 66 | 67 | [features] 68 | default = ["auth_method_memory"] 69 | auth_method_memory = [] 70 | auth_method_sql = [] 71 | -------------------------------------------------------------------------------- /server/src/authentication.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::AuthenticationRequest; 2 | use crate::CONFIG; 3 | use actix::prelude::*; 4 | use base64::decode; 5 | use jid::Jid; 6 | use log::trace; 7 | use sasl::common::Identity; 8 | use sasl::common::Identity::Username; 9 | use sasl::secret; 10 | use sasl::server::mechanisms::Plain as ServerPlain; 11 | use sasl::server::Mechanism; 12 | use sasl::server::Response; 13 | use sasl::server::Validator; 14 | use sasl::server::ValidatorError; 15 | use std::collections::HashMap; 16 | use std::str::FromStr; 17 | 18 | type Vhost = String; 19 | 20 | pub struct AuthenticationManager { 21 | authenticators: HashMap>>, 22 | } 23 | 24 | impl Default for AuthenticationManager { 25 | fn default() -> Self { 26 | Self { authenticators: HashMap::new() } 27 | } 28 | } 29 | impl AuthenticationManager { 30 | pub(crate) fn register(mut self, authenticators: &HashMap>) -> Self { 31 | CONFIG.vhosts.iter().for_each(|(vhost, config)| { 32 | config.authenticators.iter().for_each(|authenticator| match authenticator.as_ref() { 33 | "memory" => {} 34 | custom => { 35 | if let Some(recipient) = authenticators.get(custom) { 36 | self.authenticators.entry(vhost.clone()).or_insert_with(Vec::new).push(recipient.clone()); 37 | } 38 | } 39 | }); 40 | }); 41 | 42 | self 43 | } 44 | } 45 | 46 | impl Supervised for AuthenticationManager {} 47 | impl SystemService for AuthenticationManager {} 48 | 49 | impl Actor for AuthenticationManager { 50 | type Context = Context; 51 | 52 | fn started(&mut self, _ctx: &mut Self::Context) { 53 | trace!("AuthenticationManager started"); 54 | } 55 | } 56 | 57 | impl Handler for AuthenticationManager { 58 | type Result = Result; 59 | 60 | fn handle(&mut self, msg: AuthenticationRequest, _ctx: &mut Self::Context) -> Self::Result { 61 | if let Some("PLAIN") = msg.packet.mechanism() { 62 | trace!("AUTHENT WITH PLAIN"); 63 | let challenge = decode(msg.packet.challenge().as_ref().unwrap()).unwrap(); 64 | 65 | let mut mech = ServerPlain::new(MyValidator); 66 | let username = match mech.respond(&challenge) { 67 | Ok(Response::Success(Username(username), _)) => username, 68 | _ => { 69 | return Err(()); 70 | } 71 | }; 72 | 73 | Ok(Jid::from_str(&format!("{}@localhost", username)).unwrap()) 74 | } else { 75 | Err(()) 76 | } 77 | } 78 | } 79 | 80 | const USERNAME: &str = "local"; 81 | const PASSWORD: &str = "admin"; 82 | 83 | struct MyValidator; 84 | impl Validator for MyValidator { 85 | fn validate(&self, identity: &Identity, value: &secret::Plain) -> Result<(), ValidatorError> { 86 | let &secret::Plain(ref password) = value; 87 | if identity != &Identity::Username(USERNAME.to_owned()) || password != PASSWORD { 88 | Err(ValidatorError::AuthenticationFailed) 89 | } else { 90 | Ok(()) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /server/src/config.rs: -------------------------------------------------------------------------------- 1 | use config::{Config, ConfigError, File}; 2 | use serde::Deserialize; 3 | use std::collections::HashMap; 4 | 5 | #[derive(Debug, Deserialize)] 6 | pub(crate) struct Settings { 7 | pub(crate) listeners: Vec, 8 | pub(crate) authenticators: Vec, 9 | pub(crate) vhosts: HashMap, 10 | } 11 | 12 | //TODO: Rethink how to access per vhost config and config inheritance 13 | impl Settings { 14 | pub(crate) fn new() -> Result { 15 | let mut s = Config::default(); 16 | 17 | s.merge(File::with_name("config/default"))?; 18 | 19 | let default_authenticators = s.get::>("authenticators")?; 20 | 21 | let vhosts = s.get_table("vhosts")?; 22 | 23 | vhosts.iter().for_each(|(vhost, _)| { 24 | let target = format!("vhosts.{}.authenticators", vhost); 25 | 26 | let _ = s.set_default(&target, default_authenticators.clone()); 27 | }); 28 | 29 | println!("vhosts : {:?}", s.get::>("vhosts")); 30 | s.try_into() 31 | } 32 | } 33 | 34 | #[derive(Debug, Deserialize)] 35 | pub(crate) enum ListenerConfig { 36 | Tcp(TcpListenerConfig), 37 | // Ws, 38 | // Udp 39 | } 40 | 41 | #[derive(Debug, Deserialize)] 42 | pub(crate) struct TcpListenerConfig { 43 | pub(crate) port: i32, 44 | pub(crate) ip: String, 45 | pub(crate) starttls: StartTLSConfig, 46 | } 47 | 48 | #[derive(Debug, Deserialize)] 49 | pub(crate) struct Cert { 50 | pub(crate) cert_path: String, 51 | pub(crate) key_path: String, 52 | } 53 | 54 | #[derive(Debug, Deserialize)] 55 | pub(crate) enum StartTLSConfig { 56 | Unavailable, 57 | Available(Cert), 58 | Required(Cert), 59 | } 60 | 61 | impl std::fmt::Display for StartTLSConfig { 62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 63 | match self { 64 | StartTLSConfig::Unavailable => write!(f, "starttls unavailable"), 65 | StartTLSConfig::Available(_) => write!(f, "starttls available"), 66 | StartTLSConfig::Required(_) => write!(f, "starttls required"), 67 | } 68 | } 69 | } 70 | 71 | #[derive(Debug, Deserialize)] 72 | pub(crate) struct VhostConfig { 73 | pub(crate) authenticators: Vec, 74 | } 75 | -------------------------------------------------------------------------------- /server/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | 4 | use crate::config::Settings; 5 | use crate::{authentication::AuthenticationManager, sessions::manager::SessionManager}; 6 | use actix::{Actor, Recipient}; 7 | use log::info; 8 | use router::Router; 9 | use std::collections::HashMap; 10 | 11 | mod authentication; 12 | mod config; 13 | mod listeners; 14 | mod messages; 15 | mod packet; 16 | mod parser; 17 | mod router; 18 | mod sessions; 19 | #[cfg(test)] 20 | mod tests; 21 | 22 | pub use messages::AuthenticationRequest; 23 | 24 | lazy_static! { 25 | static ref CONFIG: Settings = Settings::new().unwrap(); 26 | } 27 | 28 | pub trait Service { 29 | type Config; 30 | 31 | fn create_with_config(config: &Self::Config) -> Self; 32 | } 33 | 34 | pub struct Server {} 35 | 36 | impl Server { 37 | pub fn build() -> ServerBuilder { 38 | ServerBuilder::default() 39 | } 40 | } 41 | 42 | #[derive(Default)] 43 | pub struct ServerBuilder { 44 | cert: Option, 45 | keys: Option, 46 | authenticators: HashMap>, 47 | } 48 | 49 | impl ServerBuilder { 50 | pub fn cert>(mut self, cert: T) -> Self { 51 | self.cert = Some(cert.into()); 52 | 53 | self 54 | } 55 | 56 | pub fn keys>(mut self, keys: T) -> Self { 57 | self.keys = Some(keys.into()); 58 | 59 | self 60 | } 61 | 62 | pub fn add_authenticator(mut self, authenticator_name: &str, authenticator: Recipient) -> Self { 63 | self.authenticators.insert(authenticator_name.into(), authenticator); 64 | self 65 | } 66 | 67 | pub async fn launch(self) -> std::io::Result<()> { 68 | println!("CONFIG: {:?}", *CONFIG); 69 | SessionManager::new().start(); 70 | AuthenticationManager::default().register(&self.authenticators).start(); 71 | // Starting systemd 72 | // Starting hooks 73 | // Starting clustering 74 | // Starting translation 75 | // Starting access permissions 76 | // Starting ctl 77 | // Starting commands 78 | // Starting admin 79 | // Starting Router 80 | let router = Router::new().start(); 81 | // Starting all listener (tcp, ws) 82 | for listener_cfg in CONFIG.listeners.iter() { 83 | match listener_cfg { 84 | config::ListenerConfig::Tcp(tcp_config) => { 85 | let _tcp_listener = crate::listeners::tcp::listener::TcpListener::start(tcp_config, router.clone()); 86 | } 87 | } 88 | } 89 | // let _ws_listener = crate::listeners::ws::ws_listener(); 90 | // Starting pkix 91 | // Starting ACL 92 | // Starting Shaper 93 | // Starting DB 94 | // Starting Backend 95 | // Starting Sql 96 | // Starting IQ 97 | // Starting router multicast 98 | // Starting local 99 | // Starting Session Manager 100 | // Starting s2s_in 101 | // Starting s2s_out 102 | // Starting s2s 103 | // Starting service 104 | // Starting captcha 105 | // Starting ext_mod 106 | // Starting acme 107 | // Starting auth 108 | // Starting oauth 109 | 110 | // Start API 111 | 112 | tokio::signal::ctrl_c().await.unwrap(); 113 | info!("Ctrl-C received, shutting down"); 114 | 115 | Ok(()) 116 | } 117 | } 118 | 119 | pub trait Authenticator: actix::Actor { 120 | // type Future; 121 | // type Error; 122 | 123 | // fn poll_ready(&mut self, cx: Context<'_>) -> Poll>; 124 | // fn authenticate(&mut self, request: AuthenticationRequest) -> Self::Future; 125 | } 126 | -------------------------------------------------------------------------------- /server/src/listeners.rs: -------------------------------------------------------------------------------- 1 | // TCP 2 | pub(crate) mod tcp; 3 | // WS 4 | pub(crate) mod ws; 5 | 6 | use actix_codec::AsyncRead; 7 | use tokio::io::AsyncWrite; 8 | 9 | pub trait XmppStream: AsyncRead + AsyncWrite + Unpin + Send {} 10 | pub struct XmppStreamHolder { 11 | inner: Box, 12 | } 13 | -------------------------------------------------------------------------------- /server/src/listeners/tcp.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | listeners::{XmppStream, XmppStreamHolder}, 3 | messages::{tcp::TcpOpenStream, SessionManagementPacketResult}, 4 | packet::PacketHandler, 5 | parser::codec::XmppCodec, 6 | sessions::{ 7 | state::{SessionState, StaticSessionState}, 8 | unauthenticated::UnauthenticatedSession, 9 | }, 10 | }; 11 | use actix::prelude::*; 12 | use actix_codec::{Decoder, Encoder}; 13 | use bytes::BytesMut; 14 | use log::{error, trace}; 15 | use std::io; 16 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 17 | 18 | pub(crate) mod listener; 19 | pub(crate) mod session; 20 | 21 | impl XmppStream for tokio::net::TcpStream {} 22 | impl XmppStream for tokio_rustls::server::TlsStream {} 23 | 24 | impl Handler for UnauthenticatedSession { 25 | type Result = ResponseFuture>; 26 | 27 | fn handle(&mut self, msg: TcpOpenStream, _ctx: &mut Self::Context) -> Self::Result { 28 | trace!("Opening TCP"); 29 | let mut stream = msg.stream; 30 | let acceptor = msg.acceptor; 31 | let mut buf = BytesMut::with_capacity(4096); 32 | let mut codec = XmppCodec::new(); 33 | 34 | let mut state = StaticSessionState::builder().build().unwrap(); 35 | let fut = async move { 36 | loop { 37 | stream.readable().await.unwrap(); 38 | 39 | match stream.read_buf(&mut buf).await { 40 | Ok(0) => {} 41 | Ok(_) => { 42 | while let Ok(Some(packet)) = codec.decode(&mut buf) { 43 | let r = Self::handle_packet(state.clone(), &packet).await; 44 | if let Ok(SessionManagementPacketResult { session_state, packets, .. }) = r { 45 | state.state = session_state.state; 46 | 47 | packets.into_iter().for_each(|packet| { 48 | if let Err(e) = codec.encode(packet, &mut buf) { 49 | error!("Error in proceed_packet: {:?}", e); 50 | } 51 | }); 52 | 53 | if let Err(e) = stream.write_buf(&mut buf).await { 54 | error!("{:?}", e); 55 | } 56 | 57 | if let Err(e) = stream.flush().await { 58 | error!("{:?}", e); 59 | } 60 | } 61 | } 62 | match state.state { 63 | SessionState::Negociating => break, 64 | SessionState::Closing => { 65 | // TODO: remove unwrap 66 | let _ = stream.into_std().unwrap().shutdown(std::net::Shutdown::Both); 67 | return Err(()); 68 | } 69 | _ => {} 70 | } 71 | } 72 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { 73 | continue; 74 | } 75 | Err(e) => { 76 | error!("err: {:?}", e); 77 | break; 78 | } 79 | } 80 | } 81 | 82 | match acceptor { 83 | Some(acceptor) => { 84 | trace!("Session switching to TLS"); 85 | let mut tls_stream = acceptor.accept(stream).await.unwrap(); 86 | state.state = SessionState::Negociated; 87 | let mut buf = BytesMut::with_capacity(4096); 88 | 89 | loop { 90 | match tls_stream.read_buf(&mut buf).await { 91 | Ok(0) => {} 92 | Ok(_) => { 93 | while let Ok(Some(packet)) = codec.decode(&mut buf) { 94 | if let Ok(SessionManagementPacketResult { packets, session_state, .. }) = Self::handle_packet(state.clone(), &packet).await { 95 | state.state = session_state.state; 96 | state.jid = session_state.jid; 97 | 98 | packets.into_iter().for_each(|packet| { 99 | if let Err(e) = codec.encode(packet, &mut buf) { 100 | error!("Error in proceed_packet: {:?}", e); 101 | } 102 | }); 103 | 104 | if let Err(e) = tls_stream.write_buf(&mut buf).await { 105 | error!("{:?}", e); 106 | } 107 | 108 | if let Err(e) = tls_stream.flush().await { 109 | error!("{:?}", e); 110 | } 111 | } 112 | } 113 | 114 | if state.state == SessionState::Authenticated { 115 | break; 116 | } 117 | if state.state == SessionState::Closing { 118 | // TODO: remove unwrap 119 | let (inner_stream, _) = tls_stream.into_inner(); 120 | let _ = inner_stream.into_std().unwrap().shutdown(std::net::Shutdown::Both); 121 | return Err(()); 122 | } 123 | } 124 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { 125 | continue; 126 | } 127 | Err(e) => { 128 | error!("err: {:?}", e); 129 | break; 130 | } 131 | }; 132 | } 133 | 134 | Ok((XmppStreamHolder { inner: Box::new(tls_stream) }, state)) 135 | } 136 | None => Ok((XmppStreamHolder { inner: Box::new(stream) }, state)), 137 | } 138 | }; 139 | Box::pin(fut) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /server/src/listeners/tcp/listener.rs: -------------------------------------------------------------------------------- 1 | use super::session::TcpSession; 2 | use crate::sessions::state::StaticSessionState; 3 | use crate::{config::StartTLSConfig, listeners::tcp::TcpOpenStream}; 4 | use crate::{config::TcpListenerConfig, messages::tcp::NewSession}; 5 | use crate::{listeners::XmppStreamHolder, parser::codec::XmppCodec, sessions::unauthenticated::UnauthenticatedSession}; 6 | use crate::{router::Router, sessions::Session}; 7 | use actix::{prelude::*, spawn}; 8 | use log::{error, info, trace}; 9 | use std::{ 10 | fs::File, 11 | io::{self, BufReader}, 12 | net::SocketAddr, 13 | path::Path, 14 | str::FromStr, 15 | sync::Arc, 16 | }; 17 | use tokio_rustls::{ 18 | rustls::{ 19 | internal::pemfile::{certs, pkcs8_private_keys}, 20 | Certificate, NoClientAuth, PrivateKey, ServerConfig, 21 | }, 22 | TlsAcceptor, 23 | }; 24 | use tokio_util::codec::FramedRead; 25 | 26 | pub(crate) struct TcpListener { 27 | acceptor: Option, 28 | sessions: Vec>, 29 | } 30 | 31 | impl TcpListener { 32 | pub(crate) fn new(acceptor: Option) -> Self { 33 | Self { acceptor, sessions: Vec::new() } 34 | } 35 | 36 | pub(crate) fn start(config: &TcpListenerConfig, router: Addr) -> Result, ()> { 37 | let ip = format!("{}:{}", config.ip, config.port); 38 | let socket_addr = SocketAddr::from_str(&ip).unwrap(); 39 | 40 | let acceptor = match config.starttls { 41 | StartTLSConfig::Unavailable => None, 42 | StartTLSConfig::Required(ref cert_cfg) | StartTLSConfig::Available(ref cert_cfg) => { 43 | let cert = Path::new(&cert_cfg.cert_path); 44 | let keys = Path::new(&cert_cfg.key_path); 45 | 46 | let certs = load_certs(cert).unwrap(); 47 | let mut keys = load_keys(keys).unwrap(); 48 | 49 | let mut config = ServerConfig::new(NoClientAuth::new()); 50 | config.set_single_cert(certs, keys.remove(0)).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err)).unwrap(); 51 | let acceptor = TlsAcceptor::from(Arc::new(config)); 52 | 53 | Some(acceptor) 54 | } 55 | }; 56 | 57 | let addr = Self::create(|_ctx| Self::new(acceptor)); 58 | let tcp_listener = addr.clone(); 59 | 60 | trace!("Starting new TCP listener on {} with {}", ip, config.starttls); 61 | spawn(async move { 62 | // Openning TCP to prepare for STARTLS 63 | let listener = tokio::net::TcpListener::bind(&socket_addr).await.unwrap(); 64 | 65 | while let Ok((stream, socket_addr)) = listener.accept().await { 66 | tcp_listener.do_send(NewSession(stream, socket_addr, router.clone())); 67 | } 68 | }); 69 | 70 | Ok(addr) 71 | } 72 | } 73 | 74 | impl Actor for TcpListener { 75 | type Context = Context; 76 | 77 | fn started(&mut self, _ctx: &mut Self::Context) {} 78 | } 79 | 80 | impl Handler for TcpListener { 81 | type Result = ResponseActFuture; 82 | fn handle(&mut self, msg: NewSession, _ctx: &mut Self::Context) -> Self::Result { 83 | info!("New TCP Session asked"); 84 | 85 | let router = msg.2.clone(); 86 | let acceptor = self.acceptor.clone(); 87 | let session = UnauthenticatedSession::default().start(); 88 | 89 | Box::pin( 90 | async move { session.send(TcpOpenStream { stream: msg.0, acceptor }).await.unwrap().map_err(|_| ()) } 91 | .into_actor(self) 92 | .map(|res: Result<(XmppStreamHolder, StaticSessionState), ()>, act: &mut TcpListener, _ctx| match res { 93 | Ok((stream, state)) => { 94 | trace!("Session succeed"); 95 | let session = TcpSession::create(|ctx| { 96 | let (r, w) = tokio::io::split(stream.inner); 97 | 98 | let session = Session::create(|session_ctx| Session::new(state.clone(), session_ctx.address(), ctx.address().recipient())); 99 | TcpSession::add_stream(FramedRead::new(r, XmppCodec::new()), ctx); 100 | TcpSession::new(state, 0, actix::io::FramedWrite::new(Box::pin(w), XmppCodec::new(), ctx), session) 101 | }); 102 | 103 | act.sessions.push(session) 104 | } 105 | 106 | Err(e) => { 107 | error!("Session failed {:?}", e); 108 | } 109 | }) 110 | .map(|_, _, _| { 111 | trace!("Session killed"); 112 | }), 113 | ) 114 | } 115 | } 116 | 117 | // Move to utils? 118 | fn load_certs(path: &Path) -> io::Result> { 119 | certs(&mut BufReader::new(File::open(path)?)).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert")) 120 | } 121 | 122 | fn load_keys(path: &Path) -> io::Result> { 123 | let f = File::open(path)?; 124 | pkcs8_private_keys(&mut BufReader::new(f)).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key")) 125 | } 126 | -------------------------------------------------------------------------------- /server/src/listeners/tcp/session.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::system::{PacketIn, PacketsOut, SessionCommand, SessionCommandAction}; 2 | use crate::sessions::state::{SessionState, StaticSessionState}; 3 | use crate::{parser::codec::XmppCodec, sessions::Session}; 4 | use actix::{io::FramedWrite, prelude::*}; 5 | use log::trace; 6 | use std::time::Duration; 7 | use std::{io, pin::Pin}; 8 | use tokio::io::AsyncWrite; 9 | use xmpp_proto::Packet; 10 | 11 | pub(crate) struct TcpSession { 12 | _id: usize, 13 | session: Addr, 14 | state: SessionState, 15 | #[allow(dead_code)] 16 | sink: FramedWrite>, XmppCodec>, 17 | timeout_handler: Option, 18 | } 19 | 20 | impl TcpSession { 21 | pub(crate) fn new(_state: StaticSessionState, id: usize, sink: FramedWrite>, XmppCodec>, session: Addr) -> Self { 22 | Self { 23 | _id: id, 24 | state: SessionState::Opening, 25 | sink, 26 | session, 27 | timeout_handler: None, 28 | } 29 | } 30 | 31 | fn reset_timeout(&mut self, ctx: &mut ::Context) { 32 | if let Some(handler) = self.timeout_handler { 33 | if ctx.cancel_future(handler) { 34 | trace!("Timeout handler resetted for session"); 35 | } else { 36 | trace!("Unable to reset timeout handler for session"); 37 | ctx.set_mailbox_capacity(0); 38 | self.state = SessionState::Closing; 39 | ctx.stop() 40 | } 41 | } 42 | 43 | self.timeout_handler = Some(ctx.run_later(Duration::from_secs(120), move |actor, ctx| { 44 | trace!("Duration ended"); 45 | ctx.set_mailbox_capacity(0); 46 | actor.state = SessionState::Closing; 47 | let fut = actor.session.send(SessionCommand(SessionCommandAction::Kill)).into_actor(actor).map(|_, _, _| ()); 48 | ctx.wait(fut); 49 | // ctx.stop(); 50 | })); 51 | } 52 | } 53 | 54 | impl Actor for TcpSession { 55 | type Context = Context; 56 | 57 | fn started(&mut self, _ctx: &mut Self::Context) { 58 | trace!("Starting TcpSession"); 59 | } 60 | 61 | fn stopping(&mut self, _ctx: &mut Self::Context) -> actix::Running { 62 | trace!("Stopping TcpSession"); 63 | if self.state != SessionState::Closing { 64 | self.session.do_send(SessionCommand(SessionCommandAction::Kill)); 65 | } 66 | actix::Running::Stop 67 | } 68 | 69 | fn stopped(&mut self, _ctx: &mut Self::Context) { 70 | trace!("TcpSession Stopped"); 71 | } 72 | } 73 | 74 | impl actix::io::WriteHandler for TcpSession {} 75 | 76 | impl StreamHandler> for TcpSession { 77 | fn handle(&mut self, packet: Result, ctx: &mut Context) { 78 | self.reset_timeout(ctx); 79 | 80 | if let Ok(packet) = packet { 81 | let _ = self.session.try_send(PacketIn(packet)); 82 | } 83 | } 84 | } 85 | 86 | impl Handler for TcpSession { 87 | type Result = Result<(), ()>; 88 | 89 | fn handle(&mut self, msg: PacketsOut, _ctx: &mut Self::Context) -> Self::Result { 90 | msg.0.into_iter().for_each(|packet| { 91 | self.sink.write(packet); 92 | }); 93 | 94 | Ok(()) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /server/src/listeners/ws.rs: -------------------------------------------------------------------------------- 1 | use actix::{Actor, ActorContext, StreamHandler}; 2 | use actix_web_actors::ws; 3 | 4 | /// websocket connection is long running connection, it easier 5 | /// to handle with an actor 6 | struct MyWebSocket { 7 | // hb: Instant, 8 | } 9 | 10 | impl Actor for MyWebSocket { 11 | type Context = ws::WebsocketContext; 12 | 13 | // fn started(&mut self, ctx: &mut Self::Context) { 14 | // self.hb(ctx); 15 | // } 16 | } 17 | 18 | /// Handler for `ws::Message` 19 | impl StreamHandler> for MyWebSocket { 20 | fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { 21 | // process websocket messages 22 | match msg { 23 | // Ok(ws::Message::Ping(msg)) => { 24 | // self.hb = Instant::now(); 25 | // ctx.pong(&msg); 26 | // } 27 | // Ok(ws::Message::Pong(_)) => { 28 | // self.hb = Instant::now(); 29 | // } 30 | Ok(ws::Message::Text(text)) => ctx.text(text), 31 | Ok(ws::Message::Binary(bin)) => ctx.binary(bin), 32 | Ok(ws::Message::Close(reason)) => { 33 | ctx.close(reason); 34 | ctx.stop(); 35 | } 36 | _ => ctx.stop(), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server/src/messages.rs: -------------------------------------------------------------------------------- 1 | use crate::sessions::state::StaticSessionState; 2 | use actix::Message; 3 | use jid::Jid; 4 | use std::{error::Error, fmt}; 5 | use xmpp_proto::{Auth, Packet, Stanza}; 6 | 7 | pub(crate) mod system; 8 | pub(crate) mod tcp; 9 | 10 | #[derive(Debug, Message)] 11 | #[rtype(result = "Result<(), ()>")] 12 | pub(crate) struct SessionPacket { 13 | pub(crate) packet: Packet, 14 | } 15 | 16 | #[derive(Message, derive_builder::Builder, Debug, Clone)] 17 | #[builder(setter(into, strip_option))] 18 | #[rtype("()")] 19 | pub(crate) struct SessionManagementPacketResult { 20 | #[builder(default = "Vec::new()", setter(each = "packet", into = "true"))] 21 | pub(crate) packets: Vec, 22 | pub(crate) session_state: StaticSessionState, 23 | } 24 | 25 | #[derive(Debug)] 26 | pub(crate) enum SessionManagementPacketError { 27 | Unknown, 28 | } 29 | 30 | impl Error for SessionManagementPacketError { 31 | fn description(&self) -> &str { 32 | "error" 33 | } 34 | } 35 | 36 | impl fmt::Display for SessionManagementPacketError { 37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 38 | write!(f, "error") 39 | } 40 | } 41 | 42 | impl From for SessionManagementPacketError { 43 | fn from(_: actix::MailboxError) -> Self { 44 | Self::Unknown 45 | } 46 | } 47 | 48 | impl From for SessionManagementPacketError { 49 | fn from(_: SessionManagementPacketResultBuilderError) -> Self { 50 | Self::Unknown 51 | } 52 | } 53 | 54 | #[derive(Message)] 55 | #[rtype("Result")] 56 | pub struct AuthenticationRequest { 57 | pub(crate) packet: Auth, 58 | } 59 | 60 | impl AuthenticationRequest { 61 | pub(crate) fn new(packet: Auth) -> Self { 62 | Self { packet } 63 | } 64 | } 65 | 66 | #[derive(Message)] 67 | #[rtype("()")] 68 | pub(crate) struct StanzaEnvelope { 69 | pub(crate) stanza: Stanza, 70 | pub(crate) from: StaticSessionState, 71 | } 72 | -------------------------------------------------------------------------------- /server/src/messages/system.rs: -------------------------------------------------------------------------------- 1 | use actix::{Message, Recipient}; 2 | use jid::Jid; 3 | use xmpp_proto::{Features, Packet}; 4 | 5 | #[derive(Debug, Message)] 6 | #[rtype("Result<(),()>")] 7 | pub(crate) struct RegisterSession { 8 | pub(crate) jid: Jid, 9 | pub(crate) referer: Recipient, 10 | } 11 | 12 | #[derive(Debug, Message)] 13 | #[rtype("Result<(),()>")] 14 | pub(crate) struct UnregisterSession { 15 | pub(crate) jid: Jid, 16 | } 17 | 18 | #[derive(Debug, Message)] 19 | #[rtype("Result")] 20 | pub(crate) struct GetMechanisms(pub(crate) String); 21 | 22 | #[derive(Debug, Message)] 23 | #[rtype("Result<(),()>")] 24 | pub(crate) struct SessionCommand(pub(crate) SessionCommandAction); 25 | 26 | #[derive(Debug)] 27 | pub(crate) enum SessionCommandAction { 28 | Kill, 29 | SendPacket(Packet), 30 | } 31 | 32 | /// PacketOut represents a set of packets to be sent throught the sink. 33 | /// This kind of packet must be exchange between raw session and Session. 34 | #[derive(Debug, Message)] 35 | #[rtype("Result<(),()>")] 36 | pub(crate) struct PacketsOut(pub(crate) Vec); 37 | 38 | /// PacketIn represents a packet received in a stream. 39 | /// This kind of packet must be exchange between raw session and Session. 40 | #[derive(Debug, Message)] 41 | #[rtype("()")] 42 | pub(crate) struct PacketIn(pub(crate) Packet); 43 | -------------------------------------------------------------------------------- /server/src/messages/tcp.rs: -------------------------------------------------------------------------------- 1 | use crate::listeners::XmppStreamHolder; 2 | use crate::router::Router; 3 | use crate::sessions::state::StaticSessionState; 4 | use actix::{prelude::*, Message}; 5 | use tokio::net::TcpStream; 6 | 7 | #[derive(Message)] 8 | #[rtype("()")] 9 | pub(crate) struct NewSession(pub TcpStream, pub std::net::SocketAddr, pub Addr); 10 | 11 | #[derive(Message)] 12 | #[rtype("Result<(XmppStreamHolder, StaticSessionState), ()>")] 13 | pub(crate) struct TcpOpenStream { 14 | pub(crate) stream: TcpStream, 15 | pub(crate) acceptor: Option, 16 | } 17 | -------------------------------------------------------------------------------- /server/src/packet.rs: -------------------------------------------------------------------------------- 1 | use xmpp_proto::{CloseStream, OpenStream, Packet, StreamError, StreamErrorKind}; 2 | 3 | use crate::messages::SessionManagementPacketResultBuilder; 4 | use crate::messages::{SessionManagementPacketError, SessionManagementPacketResult}; 5 | use crate::sessions::state::{SessionState, StaticSessionState}; 6 | 7 | #[async_trait::async_trait] 8 | pub(crate) trait PacketHandler { 9 | type Result; 10 | 11 | async fn handle_packet(state: StaticSessionState, stanza: &Packet) -> Self::Result; 12 | 13 | fn handle_invalid_packet( 14 | session_state: StaticSessionState, 15 | invalid_packet: &StreamErrorKind, 16 | response: &mut SessionManagementPacketResultBuilder, 17 | ) -> Result { 18 | if matches!(*invalid_packet, StreamErrorKind::UnsupportedEncoding) && SessionState::Opening.eq(&session_state.state) { 19 | return Ok(response.session_state(SessionState::UnsupportedEncoding).build()?); 20 | } 21 | 22 | if SessionState::Opening == session_state.state || SessionState::UnsupportedEncoding == session_state.state { 23 | response.packet(OpenStream::default().into()); 24 | } 25 | 26 | Self::close(response.packet(StreamError { kind: invalid_packet.clone() }.into())) 27 | } 28 | 29 | fn not_authorized_and_close(response: &mut SessionManagementPacketResultBuilder) -> Result { 30 | Self::close(response.packet(StreamError { kind: StreamErrorKind::NotAuthorized }.into())) 31 | } 32 | 33 | fn close(response: &mut SessionManagementPacketResultBuilder) -> Result { 34 | Ok(response.packet(CloseStream {}.into()).session_state(SessionState::Closing).build()?) 35 | } 36 | } 37 | 38 | #[async_trait::async_trait] 39 | pub(crate) trait StanzaHandler { 40 | async fn handle(state: StaticSessionState, stanza: &T) -> Result; 41 | } 42 | -------------------------------------------------------------------------------- /server/src/parser.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod codec; 2 | pub(crate) mod sink; 3 | -------------------------------------------------------------------------------- /server/src/parser/codec.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::sink::PacketSink; 2 | use bytes::BufMut; 3 | use circular::Buffer; 4 | use log::trace; 5 | use std::io::{self, Write}; 6 | use tokio_util::codec::{Decoder, Encoder}; 7 | use xml::ParserConfig; 8 | use xmpp_proto::Packet; 9 | 10 | /// XmppCodec deals with incoming bytes. You can feed the parser with bytes and try to detect new 11 | /// event. 12 | pub struct XmppCodec { 13 | pub sink: PacketSink, 14 | } 15 | 16 | impl XmppCodec { 17 | pub fn new() -> Self { 18 | Self { sink: Self::new_sink() } 19 | } 20 | 21 | fn new_sink() -> PacketSink { 22 | let mut cfg = ParserConfig::new().whitespace_to_characters(true); 23 | cfg.ignore_end_of_stream = true; 24 | PacketSink { 25 | parser: cfg.create_reader(Buffer::with_capacity(4096)), 26 | } 27 | } 28 | } 29 | 30 | impl Decoder for XmppCodec { 31 | type Item = Packet; 32 | 33 | type Error = io::Error; 34 | 35 | fn decode(&mut self, buf: &mut bytes::BytesMut) -> Result, Self::Error> { 36 | let _ = self.sink.parser.source_mut().write(&buf[..]); 37 | if !self.sink.parser.source().data().is_empty() { 38 | trace!("Buffer contains: {}", String::from_utf8_lossy(self.sink.parser.source().data())); 39 | } 40 | 41 | let event = match self.sink.next_packet() { 42 | Some(e) => { 43 | trace!("Decoded Packet: {:?}", e); 44 | Some(e) 45 | } 46 | _ => None, 47 | }; 48 | 49 | let l = buf.len(); 50 | let _ = buf.split_to(l); 51 | Ok(event) 52 | } 53 | } 54 | 55 | impl Encoder for XmppCodec { 56 | type Error = io::Error; 57 | 58 | fn encode(&mut self, item: Packet, dst: &mut bytes::BytesMut) -> Result<(), Self::Error> { 59 | let _ = item.write_to_stream(dst.writer()); 60 | 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /server/src/parser/sink.rs: -------------------------------------------------------------------------------- 1 | use circular::Buffer; 2 | use log::{error, trace}; 3 | use std::{borrow::Cow, io::Write}; 4 | use xml::{attribute::OwnedAttribute, name::OwnedName, namespace::Namespace, reader::ErrorKind as XmlErrorKind}; 5 | use xml::{reader::XmlEvent, EventReader, ParserConfig}; 6 | use xmpp_proto::{ns, CloseStream, Packet, PacketParsingError}; 7 | 8 | pub struct PacketSink { 9 | pub parser: EventReader, 10 | } 11 | 12 | impl PacketSink { 13 | fn reset(&mut self, saved_buffer: &[u8]) { 14 | trace!("RESETTING stream"); 15 | let mut cfg = ParserConfig::new().whitespace_to_characters(true); 16 | cfg.ignore_end_of_stream = true; 17 | self.parser = { 18 | let mut source = Buffer::with_capacity(4096); 19 | let _ = source.write_all(saved_buffer); 20 | EventReader::new_with_config(source, cfg) 21 | }; 22 | } 23 | 24 | fn parse_start_element(&mut self, name: OwnedName, namespace: Namespace, attributes: Vec) -> Result { 25 | Packet::parse(&mut self.parser, name, namespace, attributes) 26 | } 27 | 28 | pub fn next_packet(&mut self) -> Option { 29 | // Using loop for now but can be removed soon I think 30 | loop { 31 | let saved_buffer = self.parser.source().data().to_vec(); 32 | // Stop loop if buffer is empty 33 | if self.parser.source().available_data() == 0 { 34 | return None; 35 | } 36 | 37 | match self.parser.next() { 38 | Ok(xml_event) => match xml_event { 39 | XmlEvent::StartDocument { ref encoding, .. } if encoding.to_uppercase() == "UTF-8" => { 40 | continue; 41 | } 42 | 43 | XmlEvent::StartDocument { ref encoding, .. } if encoding.to_uppercase() == "UTF-8" => { 44 | return Some(Packet::InvalidPacket(Box::new(xmpp_proto::StreamErrorKind::UnsupportedEncoding))) 45 | } 46 | 47 | XmlEvent::StartElement { name, namespace, attributes } => { 48 | if let Ok(e) = self.parse_start_element(name, namespace, attributes) { 49 | return Some(e); 50 | } 51 | } 52 | 53 | XmlEvent::EndElement { name } => { 54 | if name == OwnedName::qualified("stream", ns::STREAM, Some("stream")) { 55 | return Some(CloseStream {}.into()); 56 | } else { 57 | break; 58 | } 59 | } 60 | e => { 61 | trace!("{:?}", e); 62 | continue; 63 | } 64 | }, 65 | // --> Server return but it fail our parser 66 | // Err(ref e) if e.kind().eq(&XmlErrorKind::Syntax(Cow::from("Unexpected token: continue, 67 | // Err(ref e) if e.kind().eq(&XmlErrorKind::Syntax(Cow::from("Unexpected token: continue, 68 | // Err(ref e) if e.kind().eq(&XmlErrorKind::Syntax(Cow::from("Unexpected token: continue, 69 | // Err(ref e) if e.kind().eq(&XmlErrorKind::Syntax(Cow::from("Unexpected token: continue, 70 | Err(ref e) if e.kind().eq(&XmlErrorKind::Syntax(Cow::from("Invalid processing instruction: { 71 | self.reset(&saved_buffer); 72 | continue; 73 | } 74 | // Err(ref e) if e.kind().eq(&XmlErrorKind::Syntax(Cow::from("Unexpected end of stream: still inside the root element"))) => break, 75 | Err(e) => { 76 | error!("Error {:?}", e); 77 | break; 78 | } 79 | } 80 | } 81 | 82 | None 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /server/src/router.rs: -------------------------------------------------------------------------------- 1 | use actix::{Actor, Context, Handler, Supervised, SystemService}; 2 | use log::trace; 3 | use xmpp_proto::{FromXmlElement, GenericIq, Packet, Stanza}; 4 | use xmpp_xml::Element; 5 | 6 | use crate::messages::{ 7 | system::{SessionCommand, SessionCommandAction}, 8 | StanzaEnvelope, 9 | }; 10 | 11 | /// Manage to route packet on a node 12 | #[derive(Default, Debug)] 13 | pub struct Router {} 14 | 15 | impl Router { 16 | pub(crate) fn new() -> Self { 17 | Self {} 18 | } 19 | } 20 | 21 | impl Supervised for Router {} 22 | impl SystemService for Router {} 23 | 24 | impl Actor for Router { 25 | type Context = Context; 26 | 27 | fn started(&mut self, _ctx: &mut Self::Context) { 28 | trace!("Router started"); 29 | } 30 | } 31 | 32 | impl Handler for Router { 33 | type Result = (); 34 | 35 | fn handle(&mut self, msg: StanzaEnvelope, ctx: &mut Self::Context) -> Self::Result { 36 | match msg.stanza { 37 | Stanza::IQ(iq) => { 38 | let e = if iq.get_id() == "roster" { 39 | Element::from_reader( 40 | format!( 41 | " 42 | 45 | 46 | 47 | ", 48 | iq.get_id(), 49 | "local@localhost" 50 | ) 51 | .as_bytes(), 52 | ) 53 | .unwrap() 54 | } else { 55 | Element::from_reader( 56 | format!( 57 | r#" 58 | 63 | {} 64 | 66 | 67 | 68 | 69 | "#, 70 | iq.get_id(), 71 | "localhost", 72 | "local@localhost", 73 | match iq.get_element().unwrap().get_child(1) { 74 | Some(e) => e.to_string().unwrap(), 75 | None => String::new(), 76 | } 77 | ) 78 | .as_bytes(), 79 | ) 80 | .unwrap() 81 | }; 82 | let x = GenericIq::from_element(&e).unwrap(); 83 | let response = Packet::Stanza(Box::new(Stanza::IQ(x))); 84 | 85 | let _ = msg.from.addr_session_command.unwrap().try_send(SessionCommand(SessionCommandAction::SendPacket(response))); 86 | } 87 | Stanza::Message(message) => {} 88 | Stanza::Presence(presence) => {} 89 | } 90 | 91 | () 92 | } 93 | } 94 | // /// Manage to route packet when server is the target 95 | // pub struct LocalRouter {} 96 | // /// Manage to route packet based on pattern 97 | // pub struct RegisteredRouteManager {} 98 | -------------------------------------------------------------------------------- /server/src/sessions.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::system::PacketIn; 2 | use crate::messages::system::PacketsOut; 3 | use crate::messages::system::SessionCommand; 4 | use crate::messages::system::SessionCommandAction; 5 | use crate::messages::StanzaEnvelope; 6 | use crate::router::Router; 7 | use crate::sessions::state::StaticSessionState; 8 | use crate::sessions::state::StaticSessionStateBuilder; 9 | use crate::{ 10 | messages::{system::RegisterSession, system::UnregisterSession, SessionManagementPacketError, SessionManagementPacketResult, SessionManagementPacketResultBuilder}, 11 | packet::{PacketHandler, StanzaHandler}, 12 | sessions::manager::SessionManager, 13 | sessions::state::SessionState, 14 | }; 15 | use actix::prelude::*; 16 | use jid::{BareJid, FullJid, Jid}; 17 | use log::{error, trace}; 18 | use std::convert::TryInto; 19 | use std::str::FromStr; 20 | use uuid::Uuid; 21 | use xmpp_proto::{ns, Bind, CloseStream, Features, FromXmlElement, GenericIq, IqType, NonStanza, OpenStream, Packet, Stanza, StreamError, StreamErrorKind, StreamFeatures}; 22 | use xmpp_xml::Element; 23 | 24 | pub(crate) mod manager; 25 | pub(crate) mod state; 26 | pub(crate) mod unauthenticated; 27 | 28 | const ACTIVE_SESSION_STATES: &'static [SessionState] = &[SessionState::Binding, SessionState::Binded]; 29 | 30 | /// Hold a session on a node 31 | pub struct Session { 32 | pub(crate) state: SessionState, 33 | pub(crate) sink: Recipient, 34 | jid: Jid, 35 | self_addr: Addr, 36 | } 37 | 38 | impl Session { 39 | pub(crate) fn new(state: StaticSessionState, self_addr: Addr, sink: Recipient) -> Self { 40 | Self { 41 | state: SessionState::Opening, 42 | sink, 43 | jid: state.jid.unwrap(), 44 | self_addr, 45 | } 46 | } 47 | 48 | pub(crate) fn sync_state(&mut self, StaticSessionState { state, jid, .. }: &StaticSessionState) { 49 | self.state = *state; 50 | if let Some(jid) = jid { 51 | self.jid = jid.clone(); 52 | } 53 | } 54 | } 55 | 56 | impl TryInto for &mut Session { 57 | type Error = (); 58 | 59 | fn try_into(self) -> Result { 60 | let addr = self.self_addr.clone().recipient::(); 61 | StaticSessionState::builder().state(self.state).jid(self.jid.clone()).addr_session_command(addr).build().map_err(|_| ()) 62 | } 63 | } 64 | 65 | impl Actor for Session { 66 | type Context = Context; 67 | 68 | fn started(&mut self, _ctx: &mut Self::Context) { 69 | trace!("Starting Session"); 70 | } 71 | 72 | fn stopping(&mut self, _ctx: &mut Self::Context) -> actix::Running { 73 | trace!("Stopping Session"); 74 | let _ = SessionManager::from_registry().try_send(UnregisterSession { jid: self.jid.clone() }); 75 | actix::Running::Stop 76 | } 77 | 78 | fn stopped(&mut self, _ctx: &mut Self::Context) { 79 | trace!("Session Stopped"); 80 | } 81 | } 82 | 83 | impl Handler for Session { 84 | type Result = Result<(), ()>; 85 | 86 | fn handle(&mut self, msg: SessionCommand, ctx: &mut Self::Context) -> Self::Result { 87 | match msg.0 { 88 | SessionCommandAction::SendPacket(packet) => { 89 | let _ = self.sink.try_send(PacketsOut(vec![packet])); 90 | Ok(()) 91 | } 92 | SessionCommandAction::Kill => { 93 | if let Ok(result) = Self::close(&mut SessionManagementPacketResultBuilder::default()) { 94 | self.sync_state(&result.session_state); 95 | 96 | let _ = self.sink.try_send(PacketsOut(result.packets)); 97 | ctx.stop(); 98 | } 99 | Ok(()) 100 | } 101 | } 102 | } 103 | } 104 | 105 | impl Handler for Session { 106 | type Result = ResponseActFuture; 107 | 108 | /// Different packets can be handled 109 | /// IQ(type=set, session_based_action) -> handle by the session itself 110 | /// IQ(...) -> Route to router 111 | /// Presence(self) -> handle by the session itself 112 | /// Presence(...) -> Route to router 113 | /// _ -> Route to router 114 | fn handle(&mut self, msg: PacketIn, _ctx: &mut Self::Context) -> Self::Result { 115 | let state = self.try_into().unwrap(); 116 | let fut = async move { 117 | trace!("Handle packet in session"); 118 | Self::handle_packet(state, &msg.0).await 119 | }; 120 | 121 | Box::pin(fut.into_actor(self).map(|res, act, _ctx| { 122 | if let Ok(result) = res { 123 | act.sync_state(&result.session_state); 124 | 125 | // TODO: Handle better 126 | let _ = act.sink.try_send(PacketsOut(result.packets)); 127 | } 128 | })) 129 | } 130 | } 131 | 132 | #[async_trait::async_trait] 133 | impl PacketHandler for Session { 134 | type Result = Result; 135 | 136 | async fn handle_packet(state: StaticSessionState, stanza: &Packet) -> Self::Result { 137 | match stanza { 138 | Packet::NonStanza(stanza) => >::handle(state, &**stanza).await, 139 | Packet::Stanza(stanza) if ACTIVE_SESSION_STATES.contains(&state.state) => >::handle(state, &**stanza).await, 140 | Packet::InvalidPacket(invalid_packet) => { 141 | let mut response = SessionManagementPacketResultBuilder::default(); 142 | 143 | Self::handle_invalid_packet(state, invalid_packet, &mut response) 144 | } 145 | _ => Err(SessionManagementPacketError::Unknown), 146 | } 147 | } 148 | } 149 | 150 | #[async_trait::async_trait] 151 | impl StanzaHandler for Session { 152 | async fn handle(state: StaticSessionState, stanza: &Stanza) -> Result { 153 | let fut = match stanza { 154 | Stanza::IQ(stanza) if state.state == SessionState::Binding => >::handle(state, stanza), 155 | stanza if state.state == SessionState::Binded => { 156 | Router::from_registry().send(StanzaEnvelope { stanza: stanza.clone(), from: state }).await; 157 | 158 | Box::pin(async { Err(SessionManagementPacketError::Unknown) }) 159 | } 160 | _ => Box::pin(async { Err(SessionManagementPacketError::Unknown) }), 161 | }; 162 | 163 | fut.await 164 | } 165 | } 166 | #[async_trait::async_trait] 167 | impl StanzaHandler for Session { 168 | async fn handle(state: StaticSessionState, stanza: &NonStanza) -> Result { 169 | let fut = match stanza { 170 | NonStanza::OpenStream(stanza) => >::handle(state, stanza), 171 | // NonStanza::StartTls(stanza) => Self::handle(state, stanza), 172 | // NonStanza::Auth(stanza) => Self::handle(state, stanza), 173 | // NonStanza::StreamError(stanza) => Self::handle(state, stanza), 174 | NonStanza::CloseStream(stanza) => >::handle(state, stanza), 175 | _ => Box::pin(async { Err(SessionManagementPacketError::Unknown) }), 176 | }; 177 | 178 | fut.await 179 | } 180 | } 181 | 182 | #[async_trait::async_trait] 183 | impl StanzaHandler for Session { 184 | async fn handle(_state: StaticSessionState, _stanza: &CloseStream) -> Result { 185 | Ok(SessionManagementPacketResultBuilder::default() 186 | .session_state(SessionState::Closing) 187 | .packet(CloseStream {}.into()) 188 | .build()?) 189 | } 190 | } 191 | 192 | #[async_trait::async_trait] 193 | impl StanzaHandler for Session { 194 | async fn handle(state: StaticSessionState, stanza: &OpenStream) -> Result { 195 | let mut response = SessionManagementPacketResultBuilder::default(); 196 | 197 | response 198 | .packet( 199 | OpenStream { 200 | id: Some(Uuid::new_v4().to_string()), 201 | to: stanza.from.as_ref().map(|jid| BareJid::from(jid.clone()).into()), 202 | // TODO: Replace JID crate with another? 203 | // TODO: Validate FQDN 204 | from: Jid::from_str("localhost").ok(), 205 | // TODO: Validate lang input 206 | lang: "en".into(), 207 | version: "1.0".to_string(), 208 | } 209 | .into(), 210 | ) 211 | .session_state(state.state); 212 | 213 | if stanza.version != "1.0" { 214 | return Ok(response 215 | .packet( 216 | StreamError { 217 | kind: StreamErrorKind::UnsupportedVersion, 218 | } 219 | .into(), 220 | ) 221 | .packet(CloseStream {}.into()) 222 | .session_state(SessionState::Closing) 223 | .build()?); 224 | } 225 | 226 | if stanza.to.as_ref().map(|t| t.to_string()) != Some("localhost".into()) { 227 | return Ok(response 228 | .packet(StreamError { kind: StreamErrorKind::HostUnknown }.into()) 229 | .packet(CloseStream {}.into()) 230 | .session_state(SessionState::Closing) 231 | .build()?); 232 | } 233 | 234 | match state.state { 235 | SessionState::Opening => { 236 | response 237 | .packet(StreamFeatures { features: Features::Bind }.into()) 238 | .session_state(StaticSessionStateBuilder::default().state(SessionState::Binding).build().unwrap()); 239 | } 240 | state => { 241 | error!("Action({:?}) at this stage isn't possible", state); 242 | return Self::not_authorized_and_close(&mut response); 243 | } 244 | } 245 | Ok(response.build()?) 246 | } 247 | } 248 | 249 | #[async_trait::async_trait] 250 | impl StanzaHandler for Session { 251 | async fn handle(state: StaticSessionState, stanza: &GenericIq) -> Result { 252 | let mut response = SessionManagementPacketResultBuilder::default(); 253 | 254 | response.session_state(state.state); 255 | if stanza.get_type() == IqType::Set { 256 | match state.state { 257 | SessionState::Binding => { 258 | // We expect a binding command here 259 | match stanza.get_element() { 260 | Some(element) => { 261 | match element.find((ns::BIND, "bind")) { 262 | Some(bind_element) => { 263 | let bindd = Bind::from_element(bind_element).unwrap(); 264 | let old_jid = state.jid.clone().unwrap(); 265 | let jid = FullJid::new(old_jid.clone().node().unwrap(), old_jid.domain(), bindd.resource.unwrap()); 266 | 267 | match SessionManager::from_registry() 268 | .send(RegisterSession { 269 | jid: Jid::Full(jid.clone()), 270 | referer: state.get_addr().unwrap(), 271 | }) 272 | .await 273 | .unwrap() 274 | { 275 | Ok(_) => { 276 | let mut result_element = Element::new_with_namespaces((ns::STREAM, "iq"), element); 277 | 278 | result_element 279 | .set_attr("id", stanza.get_id()) 280 | .set_attr("type", "result") 281 | .append_new_child((ns::BIND, "bind")) 282 | .append_new_child((ns::BIND, "jid")) 283 | .set_text(jid.to_string()); 284 | 285 | let result = GenericIq::from_element(&result_element).unwrap(); 286 | // its bind 287 | response 288 | .session_state(state.clone().set_state(SessionState::Binded).set_jid(Jid::Full(jid.clone()))) 289 | .packet(result.into()); 290 | } 291 | Err(_) => { 292 | trace!("Error binding session"); 293 | return Err(SessionManagementPacketError::Unknown); 294 | } 295 | } 296 | } 297 | None => { 298 | trace!("Something failed in Binding"); 299 | return Err(SessionManagementPacketError::Unknown); 300 | } 301 | } 302 | } 303 | None => { 304 | trace!("IQ without element"); 305 | return Err(SessionManagementPacketError::Unknown); 306 | } 307 | } 308 | } 309 | _ => { 310 | // trace!("Unsupported state {:?}", e); 311 | // return Err(SessionManagementPacketError::Unknown); 312 | } 313 | } 314 | } 315 | 316 | Ok(response.build()?) 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /server/src/sessions/manager.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::system::{GetMechanisms, RegisterSession, SessionCommand, UnregisterSession}; 2 | use actix::{Actor, Context, Handler, Recipient, Supervised, SystemService}; 3 | use jid::BareJid; 4 | use log::trace; 5 | use std::{collections::HashMap, convert::TryFrom}; 6 | use xmpp_proto::Features; 7 | 8 | type JidString = String; 9 | type Resource = String; 10 | 11 | /// Manage sessions on a node 12 | #[derive(Default)] 13 | pub struct SessionManager { 14 | sessions: HashMap>>, 15 | } 16 | 17 | impl SessionManager { 18 | pub(crate) fn new() -> Self { 19 | Self { sessions: HashMap::new() } 20 | } 21 | } 22 | 23 | impl Supervised for SessionManager {} 24 | impl SystemService for SessionManager {} 25 | impl Actor for SessionManager { 26 | type Context = Context; 27 | 28 | fn started(&mut self, _ctx: &mut Self::Context) { 29 | trace!("SessionManager started"); 30 | } 31 | } 32 | 33 | impl Handler for SessionManager { 34 | type Result = Result; 35 | 36 | fn handle(&mut self, _: GetMechanisms, _ctx: &mut Self::Context) -> Self::Result { 37 | Ok(Features::Mechanisms(vec!["PLAIN".into()])) 38 | } 39 | } 40 | 41 | impl Handler for SessionManager { 42 | type Result = Result<(), ()>; 43 | 44 | fn handle(&mut self, msg: RegisterSession, _ctx: &mut Self::Context) -> Self::Result { 45 | trace!("Registering session"); 46 | 47 | if let jid::Jid::Full(jid) = msg.jid { 48 | let resource = jid.resource.clone(); 49 | let sessions = self.sessions.entry(BareJid::try_from(jid).unwrap().to_string()).or_default(); 50 | 51 | if sessions.get(&resource).is_some() { 52 | trace!("Session already exists"); 53 | return Err(()); 54 | } 55 | 56 | sessions.insert(resource, msg.referer.clone()); 57 | 58 | Ok(()) 59 | } else { 60 | Err(()) 61 | } 62 | } 63 | } 64 | 65 | impl Handler for SessionManager { 66 | type Result = Result<(), ()>; 67 | 68 | fn handle(&mut self, msg: UnregisterSession, _ctx: &mut Self::Context) -> Self::Result { 69 | trace!("Unregistering session"); 70 | 71 | if let jid::Jid::Full(jid) = msg.jid { 72 | let resource = jid.resource.clone(); 73 | let bare_jid = BareJid::try_from(jid).unwrap().to_string(); 74 | if let Some(sessions) = self.sessions.get_mut(&bare_jid) { 75 | sessions.remove(&resource); 76 | trace!("Session removed: {:?}", resource); 77 | if sessions.is_empty() { 78 | self.sessions.remove(&bare_jid); 79 | trace!("No session for {:?} removed whole map", bare_jid); 80 | } 81 | } 82 | } 83 | Ok(()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /server/src/sessions/state.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::system::SessionCommand; 2 | use actix::Recipient; 3 | use jid::Jid; 4 | 5 | #[derive(Debug, Clone, Copy, PartialEq)] 6 | pub(crate) enum SessionState { 7 | Opening, 8 | Negociating, 9 | Negociated, 10 | Authenticating, 11 | Authenticated, 12 | Binding, 13 | Binded, 14 | Closing, 15 | 16 | UnsupportedEncoding, 17 | } 18 | 19 | impl Default for SessionState { 20 | fn default() -> Self { 21 | SessionState::Opening 22 | } 23 | } 24 | 25 | #[derive(derive_builder::Builder, Debug, Clone)] 26 | #[builder(setter(into))] 27 | pub(crate) struct StaticSessionState { 28 | #[builder(default = "SessionState::Opening")] 29 | pub(crate) state: SessionState, 30 | #[builder(default = "None")] 31 | pub(crate) jid: Option, 32 | #[builder(default = "None")] 33 | pub(crate) addr_session_command: Option>, 34 | } 35 | 36 | impl Default for StaticSessionState { 37 | fn default() -> Self { 38 | StaticSessionStateBuilder::default().build().unwrap() 39 | } 40 | } 41 | impl StaticSessionState { 42 | pub(crate) fn builder() -> StaticSessionStateBuilder { 43 | StaticSessionStateBuilder::default() 44 | } 45 | 46 | pub(crate) fn get_addr(&self) -> Option> { 47 | self.addr_session_command.clone() 48 | } 49 | 50 | pub(crate) fn set_jid(mut self, jid: Jid) -> Self { 51 | self.jid = Some(jid); 52 | 53 | self 54 | } 55 | 56 | /// Set the static session state's state. 57 | pub(crate) fn set_state(mut self, state: SessionState) -> Self { 58 | self.state = state; 59 | 60 | self 61 | } 62 | } 63 | 64 | impl From for StaticSessionState { 65 | fn from(state: SessionState) -> Self { 66 | Self { 67 | state, 68 | jid: None, 69 | addr_session_command: None, 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /server/src/sessions/unauthenticated.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::{system::GetMechanisms, SessionManagementPacketError}; 2 | use crate::{ 3 | authentication::AuthenticationManager, 4 | packet::{PacketHandler, StanzaHandler}, 5 | sessions::SessionManagementPacketResultBuilder, 6 | sessions::{manager::SessionManager, state::SessionState, SessionManagementPacketResult}, 7 | AuthenticationRequest, 8 | }; 9 | use actix::{Actor, Context, SystemService}; 10 | use jid::{BareJid, FullJid, Jid}; 11 | use log::{error, trace}; 12 | use std::str::FromStr; 13 | use uuid::Uuid; 14 | use xmpp_proto::{Auth, Bind, CloseStream, Features, NonStanza, OpenStream, Packet, ProceedTls, SASLSuccess, Stanza, StartTls, StreamError, StreamErrorKind, StreamFeatures}; 15 | 16 | use super::state::StaticSessionState; 17 | 18 | const OPEN_STREAM_STATES: &'static [SessionState] = &[SessionState::Opening, SessionState::Negociated, SessionState::Authenticated]; 19 | 20 | #[derive(Default)] 21 | pub(crate) struct UnauthenticatedSession { 22 | #[allow(dead_code)] 23 | pub(crate) state: SessionState, 24 | #[allow(dead_code)] 25 | pub(crate) jid: Option, 26 | } 27 | 28 | impl Actor for UnauthenticatedSession { 29 | type Context = Context; 30 | 31 | fn started(&mut self, _ctx: &mut Self::Context) { 32 | trace!("Starting UnauthenticatedSession"); 33 | } 34 | 35 | fn stopping(&mut self, _ctx: &mut Self::Context) -> actix::Running { 36 | trace!("Stopping UnauthenticatedSession"); 37 | actix::Running::Stop 38 | } 39 | 40 | fn stopped(&mut self, _ctx: &mut Self::Context) { 41 | trace!("UnauthenticatedSession Stopped"); 42 | } 43 | } 44 | 45 | #[async_trait::async_trait] 46 | impl PacketHandler for UnauthenticatedSession { 47 | type Result = Result; 48 | 49 | async fn handle_packet(state: StaticSessionState, stanza: &Packet) -> Self::Result { 50 | if state.state == SessionState::UnsupportedEncoding { 51 | let mut response = SessionManagementPacketResultBuilder::default(); 52 | 53 | return Self::handle_invalid_packet(state, &StreamErrorKind::UnsupportedEncoding, &mut response); 54 | } 55 | 56 | match stanza { 57 | Packet::NonStanza(stanza) => >::handle(state, &**stanza).await, 58 | Packet::InvalidPacket(invalid_packet) => { 59 | let mut response = SessionManagementPacketResultBuilder::default(); 60 | 61 | Self::handle_invalid_packet(state, invalid_packet, &mut response) 62 | } 63 | _ => Err(SessionManagementPacketError::Unknown), 64 | } 65 | } 66 | } 67 | 68 | #[async_trait::async_trait] 69 | impl StanzaHandler for UnauthenticatedSession { 70 | async fn handle(_state: StaticSessionState, _stanza: &Stanza) -> Result { 71 | Box::pin(async { Err(SessionManagementPacketError::Unknown) }).await 72 | } 73 | } 74 | 75 | #[async_trait::async_trait] 76 | impl StanzaHandler for UnauthenticatedSession { 77 | async fn handle(state: StaticSessionState, stanza: &NonStanza) -> Result { 78 | match stanza { 79 | NonStanza::OpenStream(stanza) if OPEN_STREAM_STATES.contains(&state.state) => >::handle(state, stanza), 80 | NonStanza::StartTls(stanza) if state.state.eq(&SessionState::Opening) => >::handle(state, stanza), 81 | NonStanza::Auth(stanza) if state.state.eq(&SessionState::Authenticating) => >::handle(state, stanza), 82 | NonStanza::StreamError(stanza) => >::handle(state, stanza), 83 | NonStanza::CloseStream(stanza) => >::handle(state, stanza), 84 | _ => Box::pin(async { Err(SessionManagementPacketError::Unknown) }), 85 | } 86 | .await 87 | } 88 | } 89 | 90 | #[async_trait::async_trait] 91 | impl StanzaHandler for UnauthenticatedSession { 92 | async fn handle(_state: StaticSessionState, _stanza: &CloseStream) -> Result { 93 | Ok(SessionManagementPacketResultBuilder::default() 94 | .session_state(SessionState::Closing) 95 | .packet(CloseStream {}.into()) 96 | .build()?) 97 | } 98 | } 99 | 100 | #[async_trait::async_trait] 101 | impl StanzaHandler for UnauthenticatedSession { 102 | async fn handle(state: StaticSessionState, stanza: &OpenStream) -> Result { 103 | let mut response = SessionManagementPacketResultBuilder::default(); 104 | 105 | response 106 | .packet( 107 | OpenStream { 108 | id: Some(Uuid::new_v4().to_string()), 109 | to: stanza.from.as_ref().map(|jid| BareJid::from(jid.clone()).into()), 110 | // TODO: Replace JID crate with another? 111 | // TODO: Validate FQDN 112 | from: Jid::from_str("localhost").ok(), 113 | // TODO: Validate lang input 114 | lang: "en".into(), 115 | version: "1.0".to_string(), 116 | } 117 | .into(), 118 | ) 119 | .session_state(state.state); 120 | 121 | if stanza.version != "1.0" { 122 | return Ok(response 123 | .packet( 124 | StreamError { 125 | kind: StreamErrorKind::UnsupportedVersion, 126 | } 127 | .into(), 128 | ) 129 | .packet(CloseStream {}.into()) 130 | .session_state(SessionState::Closing) 131 | .build()?); 132 | } 133 | 134 | if stanza.to.as_ref().map(|t| t.to_string()) != Some("localhost".into()) { 135 | return Ok(response 136 | .packet(StreamError { kind: StreamErrorKind::HostUnknown }.into()) 137 | .packet(CloseStream {}.into()) 138 | .session_state(SessionState::Closing) 139 | .build()?); 140 | } 141 | 142 | match state.state { 143 | SessionState::Opening => { 144 | response.packet(StreamFeatures { features: Features::StartTls }.into()); 145 | } 146 | 147 | SessionState::Negociated => { 148 | if let Ok(features) = SessionManager::from_registry().send(GetMechanisms("locahost".into())).await? { 149 | response.packet(StreamFeatures { features }.into()).session_state(SessionState::Authenticating); 150 | } 151 | } 152 | SessionState::Authenticated => { 153 | response.packet(StreamFeatures { features: Features::Bind }.into()).session_state(SessionState::Binding); 154 | } 155 | state => { 156 | error!("Action({:?}) at this stage isn't possible", state); 157 | return Self::not_authorized_and_close(&mut response); 158 | } 159 | } 160 | 161 | Ok(response.build()?) 162 | } 163 | } 164 | 165 | #[async_trait::async_trait] 166 | impl StanzaHandler for UnauthenticatedSession { 167 | async fn handle(_state: StaticSessionState, _stanza: &StartTls) -> Result { 168 | Ok(SessionManagementPacketResultBuilder::default() 169 | .session_state(SessionState::Negociating) 170 | .packet(ProceedTls::default().into()) 171 | .build()?) 172 | } 173 | } 174 | 175 | #[async_trait::async_trait] 176 | impl StanzaHandler for UnauthenticatedSession { 177 | async fn handle(_state: StaticSessionState, stanza: &Auth) -> Result { 178 | match AuthenticationManager::from_registry().send(AuthenticationRequest::new(stanza.clone())).await.unwrap() { 179 | Ok(jid) => Ok(SessionManagementPacketResultBuilder::default() 180 | .session_state(StaticSessionState::builder().jid(Some(jid)).state(SessionState::Authenticated).build().unwrap()) 181 | .packet(SASLSuccess::default().into()) 182 | .build()?), 183 | Err(_) => Err(SessionManagementPacketError::Unknown), 184 | } 185 | } 186 | } 187 | 188 | #[async_trait::async_trait] 189 | impl StanzaHandler for UnauthenticatedSession { 190 | async fn handle(_state: StaticSessionState, _stanza: &Bind) -> Result { 191 | Ok(SessionManagementPacketResultBuilder::default().build()?) 192 | } 193 | } 194 | 195 | #[async_trait::async_trait] 196 | impl StanzaHandler for UnauthenticatedSession { 197 | async fn handle(_state: StaticSessionState, _stanza: &StreamError) -> Result { 198 | Ok(SessionManagementPacketResultBuilder::default() 199 | .session_state(SessionState::Closing) 200 | .packet(CloseStream {}.into()) 201 | .build()?) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /server/src/tests.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod executor; 2 | mod rfc6120; 3 | -------------------------------------------------------------------------------- /server/src/tests/executor.rs: -------------------------------------------------------------------------------- 1 | use crate::packet::PacketHandler; 2 | use crate::sessions::state::StaticSessionState; 3 | use crate::sessions::{state::SessionState, unauthenticated::UnauthenticatedSession}; 4 | use xmpp_proto::Packet; 5 | 6 | pub(crate) async fn executor(packet: impl Into, expected_session_state: SessionState, starting_state: SessionState, resolver: impl Fn(Vec) -> ()) -> Result<(), ()> { 7 | let s = StaticSessionState::builder().state(starting_state).build().unwrap(); 8 | let res = UnauthenticatedSession::handle_packet(s, &packet.into()).await; 9 | if let Ok(result) = res { 10 | assert_eq!( 11 | result.session_state.state, expected_session_state, 12 | "SessionState wasn't matching: expected: {:?} | received: {:?}", 13 | expected_session_state, result.session_state.state 14 | ); 15 | resolver(result.packets); 16 | Ok(()) 17 | } else { 18 | println!("executor fail"); 19 | Err(()) 20 | } 21 | } 22 | 23 | #[macro_export] 24 | macro_rules! execute { 25 | ($packet:ident, starting_state $starting_state: expr, expected_state $state:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => { 26 | executor($packet, $state, $starting_state, |packets| { 27 | assert!(matches!(packets.as_slice(), $( $pattern )|+ $( if $guard )?), "Packets didn't matches expected ones: {:#?}", packets); 28 | }) 29 | .await 30 | }; 31 | 32 | ($packet:ident, $state:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => { 33 | execute!($packet, starting_state SessionState::Opening, expected_state $state, $( $pattern )|+ $( if $guard )?) 34 | }; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /server/src/tests/fixtures/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICljCCAX4CCQCCkuNb7meOCTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJm 3 | cjAeFw0yMTAxMDUwODIwMjRaFw0yMjAxMDUwODIwMjRaMA0xCzAJBgNVBAYTAmZy 4 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzEWH6yE14a5mDgF7aTxd 5 | aDkH42jsEMCO8b2cRGfUrU+eSRk9WCMaSL/zPhiBq7cWYKZqa9ML/myHQCFX8FVn 6 | 8rLuUCDtb+Z4fNwdccHQmiZmQz6nTKdnyz9bvnGyTj5QNnxeOmsxl/9PI32qoM/3 7 | wrRS5CQf22NgKkPGEo/nKXGkd/zIZ856c+ZXBMHnpzUQlogyJSpS/5HU6j97GpKg 8 | Vx/ETeWLLwbjEQVOWbil7uRn2YhfDVdD9G5qu4jmyKIW7v+Mq8O8ccFcJgiCEz3i 9 | 6Gz50YeUim0dRXQA/or9ix/b2eilNtn9q5DCFP2EkaBexpccBW0SLTjZTl+iOyyR 10 | AwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDGOD6IDqymUrIthvt/xoMlEGUTR0QT 11 | X62iP456RXy5s1jYFAU6f/ullQMz0CynSPiWFcShMoUUCG+fjqpDAWiplV7zQKMY 12 | dvctoSfLSyQAENu8UUhNo4s+0j/JdEve4LZENnQoY3GTZtV3qcKT8pgFIN0xJMc+ 13 | MFVNifzv83Buex2Hti/UfBBpXZyIqZLHx0NhZ4VIjj593oQqSYcDWDf6UeVqkkOb 14 | DxLt5WOHwpPWyTFyyp/yMfISXqJw/7n7FjZZJIjjP5I4T1y58lMKTDj8UNMXsy+J 15 | 8uiaqEu5wZSVglIHtTvpHkB9YXtzo6/UlgWty6fe68IRwXgZhaM7X/0c 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /server/src/tests/fixtures/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMRYfrITXhrmYO 3 | AXtpPF1oOQfjaOwQwI7xvZxEZ9StT55JGT1YIxpIv/M+GIGrtxZgpmpr0wv+bIdA 4 | IVfwVWfysu5QIO1v5nh83B1xwdCaJmZDPqdMp2fLP1u+cbJOPlA2fF46azGX/08j 5 | faqgz/fCtFLkJB/bY2AqQ8YSj+cpcaR3/Mhnznpz5lcEweenNRCWiDIlKlL/kdTq 6 | P3sakqBXH8RN5YsvBuMRBU5ZuKXu5GfZiF8NV0P0bmq7iObIohbu/4yrw7xxwVwm 7 | CIITPeLobPnRh5SKbR1FdAD+iv2LH9vZ6KU22f2rkMIU/YSRoF7GlxwFbRItONlO 8 | X6I7LJEDAgMBAAECggEADoy1Ta4j5FtDsaOxYqGGFbJaOXxztA3DILzcsJKkt2OA 9 | ZryBfhGiAaSKctXUBqMX2PKBigSSSiD40TyOwvOSX9sW1mZCA0JrOpmDD2M6tIAf 10 | sJJ54B/caMGuizYV+TS/CeJ547dW5Piubly5FpM5loi5jr5z9nBxnREOvqu5T15E 11 | MoQ9yhQtd6xJfOHPbMhyqAQeW2RwEPfzwRNWm0fH7kmQSnNgzSfBSG7R5SHXFViT 12 | v1BzjTXfapqGp6xrGzXdQCetij86GzvMMwjjgkN/Q3GExB9d/pTTAu7abZZ21d7C 13 | QjOj9lA274SS5S2FymIKFsevQ7Lr0QDlSReYZ7vNQQKBgQDyno1mkOlxsJUwTobu 14 | 0d7VrXRNxKohdCHrkyItKRLs/SWadUnc9c3YgWmLgvKlxFFtfs7kY4nbxuVAlGpC 15 | zaOf7naI7c27P7ihdfnu3GgrbkvIlTfa+LxIk98j1zERN2mmMKpoqvXf+pT+QDGm 16 | SsMIN7AqSxkRlpEAjeFZkedhOQKBgQDXiY/U4RjUV7tCuf+By3jrsaUzmEkpkgRh 17 | 72d3stk+wTdxzB9XU0v+C/u3NxbvUdv3gjMR70NkNAKf1OiGKsM19YQYeepb4MOo 18 | +62x+M8NTqb+eNle3TenTFn/yhJi/FETKwDqLVWt1FQJflfhWmXCAKffxg3U8FZW 19 | HsW3Cf/QGwKBgCcW8pNG38XIfJD25fiOacellap9+CdBrcFlyEjcaEc0lh1nrkni 20 | dJLgbt/ibjPVCIWKu8zCWNDHH+KixugSd71pz0FKhy4XGykwd99LNaFhuOYNXJ10 21 | G+nZoUcGAcrTUbtL9fi9KrY2ilDYiOdQ/lFRn5mA1f4mcyBSu68RueTJAoGBAM8v 22 | 5tkFwC5uXw3XaatFAmnejCU6oCmbuSbDUTyY6YgPh9KWGxKAea4tqrwF8r/+empD 23 | 9/+ndaqe7F7j9Njzxk7aQS6eExBa0PphZCiOOcpa7t/zH1C3acYh+OmPP3lzfiPk 24 | 1K5HAfNlBZtSnft8QBDrPHQ5GBa3KOcEEZ7Pt949AoGAQ2Wouhs7pBuRq+6RFQ1F 25 | 8KPNHsq8v8ieGBaNTJ+2AilkkKb4Zad5eS8JLpZqR1uq87BAIAehX3hk1ZR9YIez 26 | n2/uG8Lk6oEdpJBGyHQ7w6ulOyawSaXVqKvMfB7xUmMzf4SEpVXY+2DCg/0YIMvR 27 | YSIlsBWilw9Cu8ybuAEG/C8= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /server/src/tests/rfc6120.rs: -------------------------------------------------------------------------------- 1 | use crate::{execute, sessions::state::SessionState, tests::executor::executor}; 2 | use demonstrate::demonstrate; 3 | use jid::Jid; 4 | use std::str::FromStr; 5 | use uuid::Uuid; 6 | use xmpp_proto::OpenStreamBuilder; 7 | use xmpp_proto::{NonStanza, Packet, StreamError, StreamErrorKind}; 8 | 9 | mod namespaces; 10 | mod starttls; 11 | mod stream_attribute; 12 | 13 | demonstrate! { 14 | describe "when opening a Stream" { 15 | use super::*; 16 | 17 | before { 18 | let host: Jid = Jid::from_str("localhost").unwrap(); 19 | let lang: String = "en".into(); 20 | let version: String = "1.0".into(); 21 | 22 | let mut packet = OpenStreamBuilder::default() 23 | .to(host) 24 | .lang(lang) 25 | .version(version) 26 | .id(Uuid::new_v4().to_string()) 27 | .build() 28 | .unwrap(); 29 | } 30 | 31 | #[actix::test] 32 | async it "should accept a valid host" -> Result<(), ()> { 33 | execute!(packet, SessionState::Opening, [Packet::NonStanza(open_stream), ..] if matches!(**open_stream, NonStanza::OpenStream(_))) 34 | } 35 | 36 | #[actix::test] 37 | async it "should fail on invalid host" -> Result<(), ()> { 38 | packet.to = Jid::from_str("invalid").ok(); 39 | 40 | execute!(packet, SessionState::Closing, 41 | [Packet::NonStanza(open_stream), Packet::NonStanza(error), Packet::NonStanza(close)] 42 | if matches!(**open_stream, NonStanza::OpenStream(_)) && 43 | matches!(**error, NonStanza::StreamError(StreamError { kind: StreamErrorKind::HostUnknown, .. })) && 44 | matches!(**close, NonStanza::CloseStream(_)) 45 | ) 46 | } 47 | 48 | #[actix::test] 49 | async it "should fail on unsupported encoding" -> Result<(), ()> { 50 | let fail_packet = Packet::InvalidPacket(Box::new(StreamErrorKind::UnsupportedEncoding)); 51 | 52 | execute!( 53 | fail_packet, 54 | starting_state SessionState::Opening, 55 | expected_state SessionState::UnsupportedEncoding, 56 | [] 57 | ); 58 | 59 | execute!( 60 | packet, 61 | starting_state SessionState::UnsupportedEncoding, 62 | expected_state SessionState::Closing, 63 | [Packet::NonStanza(open_stream), Packet::NonStanza(error), Packet::NonStanza(close)] 64 | if matches!(**open_stream, NonStanza::OpenStream(_)) && 65 | matches!(**error, NonStanza::StreamError(StreamError { kind: StreamErrorKind::UnsupportedEncoding, .. })) && 66 | matches!(**close, NonStanza::CloseStream(_)) 67 | ) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /server/src/tests/rfc6120/namespaces.rs: -------------------------------------------------------------------------------- 1 | use crate::{execute, sessions::state::SessionState, tests::executor::executor}; 2 | use demonstrate::demonstrate; 3 | use xmpp_proto::{NonStanza, Packet, StreamError, StreamErrorKind}; 4 | 5 | demonstrate! { 6 | describe "when opening a Stream" { 7 | use super::*; 8 | 9 | #[actix::test] 10 | async it "should fail on invalid namespace" -> Result<(), ()> { 11 | let packet = Packet::InvalidPacket(Box::new(StreamErrorKind::InvalidNamespace)); 12 | 13 | execute!(packet, SessionState::Closing, 14 | [Packet::NonStanza(open_stream), Packet::NonStanza(error), Packet::NonStanza(close)] 15 | if matches!(**open_stream, NonStanza::OpenStream(_)) && 16 | matches!(**error, NonStanza::StreamError(StreamError { kind: StreamErrorKind::InvalidNamespace })) && 17 | matches!(**close, NonStanza::CloseStream(_)) 18 | ) 19 | } 20 | 21 | #[actix::test] 22 | async it "should fail on bad prefix namespace" -> Result<(), ()> { 23 | let packet = Packet::InvalidPacket(Box::new(StreamErrorKind::BadNamespacePrefix)); 24 | 25 | execute!(packet, SessionState::Closing, 26 | [Packet::NonStanza(open_stream), Packet::NonStanza(error), Packet::NonStanza(close)] 27 | if matches!(**open_stream, NonStanza::OpenStream(_)) && 28 | matches!(**error, NonStanza::StreamError(StreamError { kind: StreamErrorKind::BadNamespacePrefix })) && 29 | matches!(**close, NonStanza::CloseStream(_)) 30 | ) 31 | } 32 | 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/src/tests/rfc6120/starttls.rs: -------------------------------------------------------------------------------- 1 | use crate::{execute, sessions::state::SessionState, tests::executor::executor}; 2 | use demonstrate::demonstrate; 3 | use jid::Jid; 4 | use std::str::FromStr; 5 | use uuid::Uuid; 6 | use xmpp_proto::OpenStreamBuilder; 7 | use xmpp_proto::{NonStanza, Packet, StreamError, StreamErrorKind}; 8 | 9 | demonstrate! { 10 | describe "when opening a Stream" { 11 | use super::*; 12 | 13 | 14 | before { 15 | let host: Jid = Jid::from_str("localhost").unwrap(); 16 | let lang: String = "en".into(); 17 | let version: String = "1.0".into(); 18 | 19 | let mut packet = OpenStreamBuilder::default() 20 | .to(host) 21 | .lang(lang) 22 | .version(version) 23 | .id(Uuid::new_v4().to_string()) 24 | .build() 25 | .unwrap(); 26 | } 27 | 28 | describe "the from attribute" { 29 | use super::*; 30 | 31 | #[actix::test] 32 | async it "can be missing" -> Result<(), ()> { 33 | execute!(packet, SessionState::Opening, [Packet::NonStanza(open_stream), ..] if matches!(**open_stream, NonStanza::OpenStream(_))) 34 | } 35 | 36 | #[actix::test] 37 | async it "can be defined" -> Result<(), ()> { 38 | packet.from = Jid::from_str("alice@wonderland.lit").ok(); 39 | 40 | execute!(packet, SessionState::Opening, [Packet::NonStanza(open_stream), ..] if matches!(**open_stream, NonStanza::OpenStream(_))) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/src/tests/rfc6120/stream_attribute.rs: -------------------------------------------------------------------------------- 1 | use crate::{execute, sessions::state::SessionState, tests::executor::executor}; 2 | use demonstrate::demonstrate; 3 | use jid::Jid; 4 | use std::str::FromStr; 5 | use uuid::Uuid; 6 | use xmpp_proto::OpenStreamBuilder; 7 | use xmpp_proto::{NonStanza, Packet, StreamError, StreamErrorKind}; 8 | 9 | demonstrate! { 10 | describe "when opening a Stream" { 11 | use super::*; 12 | 13 | 14 | before { 15 | let host: Jid = Jid::from_str("localhost").unwrap(); 16 | let lang: String = "en".into(); 17 | let version: String = "1.0".into(); 18 | 19 | let mut packet = OpenStreamBuilder::default() 20 | .to(host) 21 | .lang(lang) 22 | .version(version) 23 | .id(Uuid::new_v4().to_string()) 24 | .build() 25 | .unwrap(); 26 | } 27 | 28 | describe "the from attribute" { 29 | use super::*; 30 | 31 | #[actix::test] 32 | async it "can be missing" -> Result<(), ()> { 33 | execute!(packet, SessionState::Opening, [Packet::NonStanza(open_stream), ..] if matches!(**open_stream, NonStanza::OpenStream(_))) 34 | } 35 | 36 | #[actix::test] 37 | async it "can be defined" -> Result<(), ()> { 38 | packet.from = Jid::from_str("alice@wonderland.lit").ok(); 39 | 40 | execute!(packet, SessionState::Opening, [Packet::NonStanza(open_stream), ..] if matches!(**open_stream, NonStanza::OpenStream(_))) 41 | } 42 | } 43 | 44 | describe "the to attribute" { 45 | use super::*; 46 | use xmpp_proto::OpenStream; 47 | 48 | #[actix::test] 49 | async it "must be defined" -> Result<(), ()> { 50 | packet.to = None; 51 | execute!(packet, SessionState::Closing, 52 | [Packet::NonStanza(open_stream), Packet::NonStanza(error), Packet::NonStanza(close)] 53 | if matches!(**open_stream, NonStanza::OpenStream(_)) && 54 | matches!(**error, NonStanza::StreamError(StreamError { kind: StreamErrorKind::HostUnknown, .. })) && 55 | matches!(**close, NonStanza::CloseStream(_)) 56 | ) 57 | } 58 | 59 | #[actix::test] 60 | async it "must be set to the bare jid of the initial stream header" -> Result<(), ()> { 61 | packet.from = Jid::from_str("alice@wonderland.lit/rabbithole").ok(); 62 | let expected_jid = Jid::from_str("alice@wonderland.lit").ok(); 63 | 64 | execute!(packet, SessionState::Opening, 65 | [Packet::NonStanza(open_stream), ..] 66 | if matches!(**open_stream, NonStanza::OpenStream(OpenStream {ref to, ..}) if to == &expected_jid) 67 | ) 68 | } 69 | 70 | } 71 | 72 | describe "the id attribute" { 73 | use super::*; 74 | use xmpp_proto::OpenStream; 75 | 76 | #[actix::test] 77 | async it "must be ignored if defined by initiating entity" -> Result<(), ()> { 78 | let expected_id = packet.id.clone(); 79 | 80 | execute!(packet, SessionState::Opening, 81 | [Packet::NonStanza(open_stream), ..] 82 | if matches!(**open_stream, NonStanza::OpenStream(OpenStream { ref id, .. }) if id != &expected_id) 83 | ) 84 | } 85 | } 86 | 87 | describe "the lang attribute" { 88 | use super::*; 89 | use xmpp_proto::OpenStream; 90 | 91 | #[actix::test] 92 | async it "should be defined by initiating entity" -> Result<(), ()> { 93 | 94 | execute!(packet, SessionState::Opening, 95 | [Packet::NonStanza(open_stream), ..] 96 | if matches!(**open_stream, NonStanza::OpenStream(OpenStream { ref lang, .. }) if lang == "en") 97 | ) 98 | } 99 | 100 | #[actix::test] 101 | async it "should be the default server lang if the initiating entity submit an unsupported lang" -> Result<(), ()> { 102 | packet.lang = "it".to_string(); 103 | 104 | execute!(packet, SessionState::Opening, 105 | [Packet::NonStanza(open_stream), ..] 106 | if matches!(**open_stream, NonStanza::OpenStream(OpenStream { ref lang, .. }) if lang == "en") 107 | ) 108 | } 109 | } 110 | 111 | describe "the version attribute" { 112 | use super::*; 113 | use xmpp_proto::OpenStream; 114 | 115 | #[actix::test] 116 | async it "should be 1_0" -> Result<(), ()> { 117 | 118 | execute!(packet, SessionState::Opening, 119 | [Packet::NonStanza(open_stream), ..] 120 | if matches!(**open_stream, NonStanza::OpenStream(OpenStream { ref version, .. }) if version == "1.0") 121 | ) 122 | } 123 | 124 | #[actix::test] 125 | async it "if defined under 1_0 generate an error" -> Result<(), ()> { 126 | packet.version = "0.9".into(); 127 | 128 | execute!(packet, SessionState::Closing, 129 | [Packet::NonStanza(open_stream), Packet::NonStanza(error), Packet::NonStanza(close)] 130 | if matches!(**open_stream, NonStanza::OpenStream(_)) && 131 | matches!(**error, NonStanza::StreamError(StreamError { kind: StreamErrorKind::UnsupportedVersion, .. })) && 132 | matches!(**close, NonStanza::CloseStream(_)) 133 | ) 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | //! xmpp-rs is an implementation of the Extensible Messaging and Presence Protocol (XMPP). 3 | //! Based on tokio-rs and futures-rs. It's goal is to be fully tested and usable. 4 | //! 5 | //! It allow you to create a client to talk with any XMPP server or to use the proto lib to make 6 | //! your own plugins/component. 7 | //! 8 | //! This implementation focus is to be usable and tested. 9 | //! 10 | /// Reexport of XMPPServer 11 | pub extern crate xmpp_server as server; 12 | -------------------------------------------------------------------------------- /xml/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xmpp-xml" 3 | version = "0.1.3" 4 | license = "MPL-2.0" 5 | 6 | edition = "2018" 7 | description = """ 8 | xmpp-rs is an implementation of the Extensible Messaging and Presence Protocol (XMPP). 9 | 10 | Based on tokio-rs and futures-rs. It's goal is to be fully tested and usable. 11 | """ 12 | repository = "https://github.com/Freyskeyd/xmpp-rs" 13 | keywords = ["xmpp", "tokio", "jabber", "IM", "instant-messaging"] 14 | categories = ["network-programming"] 15 | 16 | [dependencies] 17 | xml-rs = "*" 18 | string_cache = "0" 19 | -------------------------------------------------------------------------------- /xml/src/children.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::btree_map::Iter as BTreeMapIter; 3 | 4 | use crate::{Element, QName}; 5 | 6 | /// An iterator over children of an element. 7 | pub struct Children<'a> { 8 | pub(crate) idx: usize, 9 | pub(crate) element: &'a Element, 10 | } 11 | 12 | /// A mutable iterator over children of an element. 13 | pub struct ChildrenMut<'a> { 14 | pub(crate) iter: ::std::slice::IterMut<'a, Element>, 15 | } 16 | 17 | /// An iterator over attributes of an element. 18 | pub struct Attrs<'a> { 19 | pub(crate) iter: BTreeMapIter<'a, QName<'a>, String>, 20 | } 21 | 22 | /// An iterator over matching children. 23 | pub struct FindChildren<'a> { 24 | pub(crate) tag: Cow<'a, QName<'a>>, 25 | pub(crate) child_iter: Children<'a>, 26 | } 27 | 28 | /// A mutable iterator over matching children. 29 | pub struct FindChildrenMut<'a> { 30 | pub(crate) tag: Cow<'a, QName<'a>>, 31 | pub(crate) child_iter: ChildrenMut<'a>, 32 | } 33 | 34 | impl<'a> Iterator for Children<'a> { 35 | type Item = &'a Element; 36 | 37 | fn next(&mut self) -> Option<&'a Element> { 38 | if self.idx < self.element.children.len() { 39 | let rv = &self.element.children[self.idx]; 40 | self.idx += 1; 41 | Some(rv) 42 | } else { 43 | None 44 | } 45 | } 46 | } 47 | 48 | impl<'a> Iterator for ChildrenMut<'a> { 49 | type Item = &'a mut Element; 50 | 51 | fn next(&mut self) -> Option<&'a mut Element> { 52 | self.iter.next() 53 | } 54 | } 55 | 56 | impl<'a> Iterator for FindChildren<'a> { 57 | type Item = &'a Element; 58 | 59 | fn next(&mut self) -> Option<&'a Element> { 60 | use std::borrow::Borrow; 61 | loop { 62 | if let Some(child) = self.child_iter.next() { 63 | if child.tag() == self.tag.borrow() { 64 | return Some(child); 65 | } 66 | } else { 67 | return None; 68 | } 69 | } 70 | } 71 | } 72 | 73 | impl<'a> Iterator for FindChildrenMut<'a> { 74 | type Item = &'a mut Element; 75 | 76 | fn next(&mut self) -> Option<&'a mut Element> { 77 | use std::borrow::Borrow; 78 | let tag: &QName = &self.tag.borrow(); 79 | self.child_iter.find(|x| x.tag() == tag) 80 | } 81 | } 82 | 83 | impl<'a> Iterator for Attrs<'a> { 84 | type Item = (&'a QName<'a>, &'a str); 85 | 86 | fn next(&mut self) -> Option<(&'a QName<'a>, &'a str)> { 87 | if let Some((k, v)) = self.iter.next() { 88 | Some((k, v.as_str())) 89 | } else { 90 | None 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /xml/src/element.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::BTreeMap; 3 | use std::io::{Read, Write}; 4 | use std::mem; 5 | use std::sync::Arc; 6 | 7 | use xml::attribute::{Attribute, OwnedAttribute}; 8 | use xml::common::XmlVersion; 9 | use xml::name::{Name, OwnedName}; 10 | use xml::namespace::{Namespace as XmlNamespaceMap, NS_EMPTY_URI, NS_XMLNS_URI, NS_XML_URI}; 11 | use xml::reader::{EventReader, ParserConfig, XmlEvent}; 12 | use xml::writer::{EventWriter, XmlEvent as XmlWriteEvent}; 13 | use xml::EmitterConfig; 14 | 15 | use crate::{AsQName, Attrs, Children, ChildrenMut, Error, FindChildren, FindChildrenMut, NamespaceMap, Position, QName, WriteOptions, XmlProlog}; 16 | 17 | /// Represents an XML element. 18 | /// 19 | /// Usually constructed from either parsing or one of the two constructors 20 | /// an element is part of a tree and represents an XML element and the 21 | /// children contained. 22 | /// 23 | /// Imagine a structure like this: 24 | /// 25 | /// ```xml 26 | ///

Hello World!

27 | /// ``` 28 | /// 29 | /// In this case the structure is more or less represented like this: 30 | /// 31 | /// ```ignore 32 | /// Element { 33 | /// tag: "p", 34 | /// text: "Hello ", 35 | /// tail: None, 36 | /// children: [ 37 | /// Element { 38 | /// tag: "strong", 39 | /// text: "World", 40 | /// tail: Some("!") 41 | /// } 42 | /// ] 43 | /// } 44 | /// ``` 45 | /// 46 | /// Namespaces are internally managed and inherited downwards when an 47 | /// element is created. 48 | #[derive(Debug, Clone)] 49 | pub struct Element { 50 | tag: QName<'static>, 51 | attributes: BTreeMap, String>, 52 | pub(crate) children: Vec, 53 | nsmap: Option>, 54 | emit_nsmap: bool, 55 | text: Option, 56 | tail: Option, 57 | write_end_tag: bool, 58 | } 59 | impl Element { 60 | /// Creates a new element without any children but a given tag. 61 | /// 62 | /// This can be used at all times to create a new element however when you 63 | /// work with namespaces it's recommended to only use this for the root 64 | /// element and then create further children through `new_with_namespaces` 65 | /// as otherwise namespaces will not be propagaged downwards properly. 66 | pub fn new<'a, Q: AsQName<'a>>(tag: Q) -> Element { 67 | Element::new_with_nsmap(&tag.as_qname(), None) 68 | } 69 | 70 | /// Creates a new element without any children but inheriting the 71 | /// namespaces from another element. 72 | /// 73 | /// This has the advantage that internally the map will be shared 74 | /// across elements for as long as no further modifications are 75 | /// taking place. 76 | pub fn new_with_namespaces<'a, Q: AsQName<'a>>(tag: Q, reference: &Element) -> Element { 77 | Element::new_with_nsmap(&tag.as_qname(), reference.nsmap.clone()) 78 | } 79 | 80 | fn new_with_nsmap(tag: &QName<'_>, nsmap: Option>) -> Element { 81 | let mut rv = Element { 82 | tag: tag.share(), 83 | attributes: BTreeMap::new(), 84 | nsmap, 85 | emit_nsmap: false, 86 | children: vec![], 87 | text: None, 88 | tail: None, 89 | write_end_tag: true, 90 | }; 91 | if let Some(url) = tag.ns() { 92 | let prefix = rv.get_namespace_prefix(url).unwrap_or("").to_string(); 93 | rv.register_namespace(url, Some(&prefix)); 94 | } 95 | rv 96 | } 97 | 98 | /// Parses some XML data into an `Element` from a reader. 99 | pub fn from_reader(r: R) -> Result { 100 | let cfg = ParserConfig::new().whitespace_to_characters(true); 101 | let mut reader = cfg.create_reader(r); 102 | loop { 103 | match reader.next() { 104 | Ok(XmlEvent::StartElement { name, attributes, namespace }) => { 105 | return Element::from_start_element(name, attributes, namespace, None, &mut reader); 106 | } 107 | Ok(XmlEvent::Comment(..)) | Ok(XmlEvent::Whitespace(..)) | Ok(XmlEvent::StartDocument { .. }) | Ok(XmlEvent::ProcessingInstruction { .. }) => { 108 | continue; 109 | } 110 | Ok(_) => { 111 | return Err(Error::UnexpectedEvent { 112 | msg: Cow::Borrowed("xml construct"), 113 | pos: Position::from_xml_position(&reader), 114 | }) 115 | } 116 | Err(e) => return Err(e.into()), 117 | } 118 | } 119 | } 120 | 121 | /// Dump an element as XML document into a writer. 122 | /// 123 | /// This will create an XML document with a processing instruction 124 | /// to start it. There is currently no API to only serialize a non 125 | /// standalone element. 126 | /// 127 | /// Currently the writer has no way to customize what is generated 128 | /// in particular there is no support yet for automatically indenting 129 | /// elements. The reason for this is that there is no way to ignore 130 | /// this information automatically in the absence of DTD support which 131 | /// is not really planned. 132 | pub fn to_writer(&self, w: W) -> Result<(), Error> { 133 | self.to_writer_with_options(w, WriteOptions::new()) 134 | } 135 | 136 | /// Dump an element as XML document into a writer with option. 137 | /// 138 | /// This will create an XML document with a processing instruction 139 | /// to start it. There is currently no API to only serialize a non 140 | /// standalone element. 141 | /// 142 | /// Currently the writer has no way to customize what is generated 143 | /// in particular there is no support yet for automatically indenting 144 | /// elements. The reason for this is that there is no way to ignore 145 | /// this information automatically in the absence of DTD support which 146 | /// is not really planned. 147 | pub fn to_writer_with_options(&self, w: W, options: WriteOptions) -> Result<(), Error> { 148 | let mut writer = EmitterConfig::new() 149 | .normalize_empty_elements(self.write_end_tag) 150 | .write_document_declaration(options.xml_prolog.is_some()) 151 | .create_writer(w); 152 | 153 | if options.xml_prolog.is_some() { 154 | writer.write(XmlWriteEvent::StartDocument { 155 | version: match options.xml_prolog.unwrap() { 156 | XmlProlog::Version10 => XmlVersion::Version10, 157 | XmlProlog::Version11 => XmlVersion::Version11, 158 | }, 159 | encoding: Some("utf-8"), 160 | standalone: None, 161 | })?; 162 | } 163 | 164 | self.dump_into_writer(&mut writer) 165 | } 166 | 167 | /// Dump an element as XML document into a string 168 | pub fn to_string(&self) -> Result { 169 | let mut out: Vec = Vec::new(); 170 | self.to_writer(&mut out)?; 171 | Ok(String::from_utf8(out).unwrap()) 172 | } 173 | 174 | fn get_xml_name<'a>(&'a self, qname: &'a QName<'a>) -> Name<'a> { 175 | let mut name = Name::local(qname.name()); 176 | if let Some(url) = qname.ns() { 177 | name.namespace = Some(url); 178 | if let Some(prefix) = self.get_namespace_prefix(url) { 179 | if !prefix.is_empty() { 180 | name.prefix = Some(prefix); 181 | } 182 | } 183 | } 184 | name 185 | } 186 | 187 | fn dump_into_writer(&self, w: &mut EventWriter) -> Result<(), Error> { 188 | let name = self.get_xml_name(&self.tag); 189 | 190 | let mut attributes = Vec::with_capacity(self.attributes.len()); 191 | for (k, v) in self.attributes.iter() { 192 | attributes.push(Attribute { name: self.get_xml_name(k), value: v }); 193 | } 194 | 195 | let mut namespace = XmlNamespaceMap::empty(); 196 | if self.emit_nsmap { 197 | if let Some(ref nsmap) = self.nsmap { 198 | for (prefix, url) in &nsmap.prefix_to_ns { 199 | namespace.put(prefix.borrow(), url.borrow()); 200 | } 201 | } 202 | } 203 | 204 | w.write(XmlWriteEvent::StartElement { 205 | name, 206 | attributes: Cow::Owned(attributes), 207 | namespace: Cow::Owned(namespace), 208 | })?; 209 | 210 | let text = self.text(); 211 | if !text.is_empty() { 212 | w.write(XmlWriteEvent::Characters(text))?; 213 | } 214 | 215 | for elem in &self.children { 216 | elem.dump_into_writer(w)?; 217 | let text = elem.tail(); 218 | if !text.is_empty() { 219 | w.write(XmlWriteEvent::Characters(text))?; 220 | } 221 | } 222 | 223 | if self.write_end_tag { 224 | w.write(XmlWriteEvent::EndElement { name: Some(name) })?; 225 | } 226 | 227 | Ok(()) 228 | } 229 | 230 | pub fn from_xml_start_element(start_element: &xml::reader::XmlEvent, reader: &mut EventReader) -> Result { 231 | match start_element { 232 | XmlEvent::StartElement { name, attributes, namespace } => Self::from_start_element(name.to_owned(), attributes.to_owned(), namespace.to_owned(), None, reader), 233 | _ => Err(Error::DuplicateNamespacePrefix), 234 | } 235 | } 236 | pub fn from_start_element( 237 | name: OwnedName, 238 | attributes: Vec, 239 | namespace: XmlNamespaceMap, 240 | parent_nsmap: Option>, 241 | reader: &mut EventReader, 242 | ) -> Result { 243 | let mut root = Element { 244 | tag: QName::from_owned_name(name), 245 | attributes: BTreeMap::new(), 246 | nsmap: parent_nsmap, 247 | emit_nsmap: false, 248 | children: vec![], 249 | text: None, 250 | tail: None, 251 | write_end_tag: true, 252 | }; 253 | for attr in attributes { 254 | root.attributes.insert(QName::from_owned_name(attr.name), attr.value); 255 | } 256 | 257 | if !namespace.is_essentially_empty() { 258 | for (prefix, url) in namespace.0.iter() { 259 | root.register_namespace(url, Some(prefix)); 260 | } 261 | }; 262 | 263 | root.parse_children(reader)?; 264 | Ok(root) 265 | } 266 | 267 | fn parse_children(&mut self, reader: &mut EventReader) -> Result<(), Error> { 268 | loop { 269 | match reader.next() { 270 | Ok(XmlEvent::EndElement { ref name }) => { 271 | if name.local_name == self.tag.name() && name.namespace.as_deref() == self.tag.ns() { 272 | return Ok(()); 273 | } else { 274 | return Err(Error::UnexpectedEvent { 275 | msg: Cow::Owned(format!("Unexpected end element {}", &name.local_name)), 276 | pos: Position::from_xml_position(reader), 277 | }); 278 | } 279 | } 280 | Ok(XmlEvent::StartElement { name, attributes, namespace }) => { 281 | self.children.push(Element::from_start_element(name, attributes, namespace, self.nsmap.clone(), reader)?); 282 | } 283 | Ok(XmlEvent::Characters(s)) => { 284 | let child_count = self.children.len(); 285 | if child_count > 0 { 286 | self.children[child_count - 1].tail = Some(s); 287 | } else { 288 | self.text = Some(s); 289 | } 290 | } 291 | Ok(XmlEvent::CData(s)) => { 292 | self.text = Some(s); 293 | } 294 | Ok(XmlEvent::Comment(..)) | Ok(XmlEvent::Whitespace(..)) | Ok(XmlEvent::StartDocument { .. }) | Ok(XmlEvent::ProcessingInstruction { .. }) => { 295 | continue; 296 | } 297 | Ok(_) => { 298 | return Err(Error::UnexpectedEvent { 299 | msg: Cow::Borrowed("unknown element"), 300 | pos: Position::from_xml_position(reader), 301 | }) 302 | } 303 | Err(e) => { 304 | return Err(e.into()); 305 | } 306 | } 307 | } 308 | } 309 | 310 | /// Returns the text of a tag. 311 | /// 312 | /// Note that this does not trim or modify whitespace so the return 313 | /// value might contain structural information from the XML file. 314 | pub fn text(&self) -> &str { 315 | self.text.as_deref().unwrap_or("") 316 | } 317 | 318 | /// Sets a new text value for the tag. 319 | pub fn set_text>(&mut self, value: S) -> &mut Element { 320 | let value = value.into(); 321 | if value.is_empty() { 322 | self.text = None; 323 | } else { 324 | self.text = Some(value); 325 | } 326 | self 327 | } 328 | 329 | pub fn write_end_tag(mut self, value: bool) -> Self { 330 | self.write_end_tag = value; 331 | 332 | self 333 | } 334 | 335 | /// Returns the tail text of a tag. 336 | /// 337 | /// The tail is the text following an element. 338 | pub fn tail(&self) -> &str { 339 | self.tail.as_deref().unwrap_or("") 340 | } 341 | 342 | /// Sets a new tail text value for the tag. 343 | pub fn set_tail>(&mut self, value: S) -> &mut Element { 344 | let value = value.into(); 345 | if value.is_empty() { 346 | self.tail = None; 347 | } else { 348 | self.tail = Some(value); 349 | } 350 | self 351 | } 352 | 353 | /// The tag of the element as qualified name. 354 | /// 355 | /// Use the `QName` functionality to extract the information from the 356 | /// tag name you care about (like the local name). 357 | pub fn tag(&self) -> &QName { 358 | &self.tag 359 | } 360 | 361 | /// Sets a new tag for the element. 362 | pub fn set_tag<'a>(&mut self, tag: &QName<'a>) -> &mut Element { 363 | self.tag = tag.share(); 364 | self 365 | } 366 | 367 | /// Returns the number of children 368 | pub fn child_count(&self) -> usize { 369 | self.children.len() 370 | } 371 | 372 | /// Returns the nth child. 373 | pub fn get_child(&self, idx: usize) -> Option<&Element> { 374 | self.children.get(idx) 375 | } 376 | 377 | /// Returns the nth child as a mutable reference. 378 | pub fn get_child_mut(&mut self, idx: usize) -> Option<&mut Element> { 379 | self.children.get_mut(idx) 380 | } 381 | 382 | /// Removes a child. 383 | /// 384 | /// This returns the element if it was removed or None if the 385 | /// index was out of bounds. 386 | pub fn remove_child(&mut self, idx: usize) -> Option { 387 | if self.children.len() > idx { 388 | Some(self.children.remove(idx)) 389 | } else { 390 | None 391 | } 392 | } 393 | 394 | /// Appends a new child and returns a reference to self. 395 | pub fn append_child(&mut self, child: Element) -> &mut Element { 396 | self.children.push(child); 397 | self 398 | } 399 | 400 | /// Appends a new child to the element and returns a reference to it. 401 | /// 402 | /// This uses ``Element::new_with_namespaces`` internally and can 403 | /// then be used like this: 404 | /// 405 | /// ``` 406 | /// use xmpp_xml::Element; 407 | /// 408 | /// let ns = "http://example.invalid/#ns"; 409 | /// let mut root = Element::new((ns, "mydoc")); 410 | /// 411 | /// { 412 | /// let mut list = root.append_new_child((ns, "list")); 413 | /// for x in 0..3 { 414 | /// list.append_new_child((ns, "item")).set_text(format!("Item {}", x)); 415 | /// } 416 | /// } 417 | /// ``` 418 | pub fn append_new_child<'a, Q: AsQName<'a>>(&'a mut self, tag: Q) -> &'a mut Element { 419 | let child = Element::new_with_namespaces(tag, self); 420 | self.append_child(child); 421 | let idx = self.children.len() - 1; 422 | &mut self.children[idx] 423 | } 424 | 425 | /// Returns an iterator over all children. 426 | pub fn children(&self) -> Children<'_> { 427 | Children { idx: 0, element: self } 428 | } 429 | 430 | /// Returns a mutable iterator over all children. 431 | pub fn children_mut(&mut self) -> ChildrenMut<'_> { 432 | ChildrenMut { iter: self.children.iter_mut() } 433 | } 434 | 435 | /// Returns all children with the given name. 436 | pub fn find_all<'a, Q: AsQName<'a>>(&'a self, tag: Q) -> FindChildren<'a> { 437 | FindChildren { 438 | tag: tag.as_qname(), 439 | child_iter: self.children(), 440 | } 441 | } 442 | 443 | /// Returns all children with the given name. 444 | pub fn find_all_mut<'a, Q: AsQName<'a>>(&'a mut self, tag: Q) -> FindChildrenMut<'a> { 445 | FindChildrenMut { 446 | tag: tag.as_qname(), 447 | child_iter: self.children_mut(), 448 | } 449 | } 450 | 451 | /// Finds the first matching child 452 | pub fn find<'a, Q: AsQName<'a>>(&'a self, tag: Q) -> Option<&'a Element> { 453 | use std::borrow::Borrow; 454 | let tag = tag.as_qname(); 455 | 456 | for child in self.children() { 457 | if child.tag() == tag.borrow() { 458 | return Some(child); 459 | } 460 | } 461 | None 462 | } 463 | 464 | /// Finds the first matching child and returns a mut ref 465 | pub fn find_mut<'a, Q: AsQName<'a>>(&'a mut self, tag: Q) -> Option<&'a mut Element> { 466 | self.find_all_mut(tag).next() 467 | } 468 | 469 | /// Look up an attribute by qualified name. 470 | pub fn get_attr<'a, Q: AsQName<'a>>(&'a self, name: Q) -> Option<&'a str> { 471 | self.attributes.get(&name.as_qname()).map(|x| x.as_str()) 472 | } 473 | 474 | /// Sets a new attribute. 475 | /// 476 | /// This returns a reference to the element so you can chain the calls. 477 | pub fn set_attr<'a, Q: AsQName<'a>, S: Into>(&'a mut self, name: Q, value: S) -> &'a mut Element { 478 | self.attributes.insert(name.as_qname().share(), value.into()); 479 | self 480 | } 481 | 482 | /// Removes an attribute and returns the stored string. 483 | pub fn remove_attr<'a, Q: AsQName<'a>>(&'a mut self, name: Q) -> Option { 484 | // so this requires some explanation. We store internally QName<'static> 485 | // which means the QName has a global lifetime. This works because we 486 | // move the internal string storage into a global string cache or we are 487 | // pointing to static memory in the binary. 488 | // 489 | // However while Rust can coerce our BTreeMap from QName<'static> to 490 | // QName<'a> when reading, we can't do the same when writing. This is 491 | // to prevent us from stashing a QName<'a> into the btreemap. However on 492 | // remove that restriction makes no sense so we can unsafely transmute it 493 | // away. I wish there was a better way though. 494 | use std::borrow::Borrow; 495 | let name = name.as_qname(); 496 | let name_ref: &QName<'a> = name.borrow(); 497 | let name_ref_static: &QName<'static> = unsafe { mem::transmute(name_ref) }; 498 | self.attributes.remove(name_ref_static) 499 | } 500 | 501 | /// Returns an iterator over all attributes 502 | pub fn attrs(&self) -> Attrs<'_> { 503 | Attrs { iter: self.attributes.iter() } 504 | } 505 | 506 | /// Count the attributes 507 | pub fn attr_count(&self) -> usize { 508 | self.attributes.len() 509 | } 510 | 511 | fn get_nsmap_mut(&mut self) -> &mut NamespaceMap { 512 | let new_map = match self.nsmap { 513 | Some(ref mut nsmap) if Arc::strong_count(nsmap) == 1 => None, 514 | Some(ref mut nsmap) => Some(Arc::new((**nsmap).clone())), 515 | None => Some(Arc::new(NamespaceMap::new())), 516 | }; 517 | if let Some(nsmap) = new_map { 518 | self.nsmap = Some(nsmap); 519 | } 520 | Arc::get_mut(self.nsmap.as_mut().unwrap()).unwrap() 521 | } 522 | 523 | /// Registers a namespace with the internal namespace map. 524 | /// 525 | /// Note that there is no API to remove namespaces from an element once 526 | /// the namespace has been set so be careful with modifying this! 527 | /// 528 | /// This optionally also registers a specific prefix however if that prefix 529 | /// is already used a random one is used instead. 530 | pub fn register_namespace(&mut self, url: &str, prefix: Option<&str>) { 531 | if self.get_namespace_prefix(url).is_none() && self.get_nsmap_mut().register_if_missing(url, prefix) { 532 | self.emit_nsmap = true; 533 | } 534 | } 535 | 536 | /// Sets a specific namespace prefix. This will also register the 537 | /// namespace if it was unknown so far. 538 | /// 539 | /// In case a prefix is set that is already set elsewhere an error is 540 | /// returned. It's recommended that this method is only used on the 541 | /// root node before other prefixes are added. 542 | pub fn set_namespace_prefix(&mut self, url: &str, prefix: &str) -> Result<(), Error> { 543 | if self.get_namespace_prefix(url) == Some(prefix) { 544 | Ok(()) 545 | } else { 546 | self.get_nsmap_mut().set_prefix(url, prefix) 547 | } 548 | } 549 | 550 | /// Returns the assigned prefix for a namespace. 551 | pub fn get_namespace_prefix(&self, url: &str) -> Option<&str> { 552 | match url { 553 | NS_EMPTY_URI => Some(""), 554 | NS_XML_URI => Some("xml"), 555 | NS_XMLNS_URI => Some("xmlns"), 556 | _ => { 557 | if let Some(ref nsmap) = self.nsmap { 558 | nsmap.get_prefix(url) 559 | } else { 560 | None 561 | } 562 | } 563 | } 564 | } 565 | 566 | /// Finds the first element that match a given path downwards 567 | pub fn navigate<'a, Q: AsQName<'a>>(&'a self, path: &[Q]) -> Option<&'a Element> { 568 | use std::borrow::Borrow; 569 | let mut node = self; 570 | 571 | 'outer: for piece in path { 572 | let reftag = piece.as_qname(); 573 | for child in node.children() { 574 | if child.tag() == reftag.borrow() { 575 | node = child; 576 | continue 'outer; 577 | } 578 | } 579 | return None; 580 | } 581 | 582 | Some(node) 583 | } 584 | } 585 | -------------------------------------------------------------------------------- /xml/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, fmt, io, str::Utf8Error}; 2 | 3 | use xml::reader::{Error as XmlReadError, ErrorKind as XmlReadErrorKind}; 4 | use xml::writer::Error as XmlWriteError; 5 | 6 | use crate::Position; 7 | 8 | /// Errors that can occur parsing XML 9 | #[derive(Debug)] 10 | pub enum Error { 11 | /// The XML is invalid 12 | MalformedXml { msg: Cow<'static, str>, pos: Position }, 13 | /// An IO Error 14 | Io(io::Error), 15 | /// A UTF-8 Error 16 | Utf8(Utf8Error), 17 | /// This library is unable to process this XML. This can occur if, for 18 | /// example, the XML contains processing instructions. 19 | UnexpectedEvent { msg: Cow<'static, str>, pos: Position }, 20 | /// A namespace prefix was already used 21 | DuplicateNamespacePrefix, 22 | } 23 | 24 | impl Error { 25 | /// Returns the position of the error if known 26 | pub fn position(&self) -> Option { 27 | match *self { 28 | Error::MalformedXml { pos, .. } => Some(pos), 29 | Error::UnexpectedEvent { pos, .. } => Some(pos), 30 | _ => None, 31 | } 32 | } 33 | 34 | /// Returns the line number of the error or 0 if unknown 35 | pub fn line(&self) -> u64 { 36 | self.position().map(|x| x.line()).unwrap_or(0) 37 | } 38 | 39 | /// Returns the column of the error or 0 if unknown 40 | pub fn column(&self) -> u64 { 41 | self.position().map(|x| x.column()).unwrap_or(0) 42 | } 43 | } 44 | 45 | impl fmt::Display for Error { 46 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 47 | match *self { 48 | Error::MalformedXml { ref pos, ref msg } => write!(f, "Malformed XML: {} ({})", msg, pos), 49 | Error::Io(ref e) => write!(f, "{}", e), 50 | Error::Utf8(ref e) => write!(f, "{}", e), 51 | Error::UnexpectedEvent { ref msg, .. } => write!(f, "Unexpected XML event: {}", msg), 52 | Error::DuplicateNamespacePrefix => write!(f, "Encountered duplicated namespace prefix"), 53 | } 54 | } 55 | } 56 | 57 | impl std::error::Error for Error { 58 | fn description(&self) -> &str { 59 | match *self { 60 | Error::MalformedXml { .. } => "Malformed XML", 61 | Error::Io(..) => "IO error", 62 | Error::Utf8(..) => "utf-8 error", 63 | Error::UnexpectedEvent { .. } => "Unexpected XML element", 64 | Error::DuplicateNamespacePrefix => "Duplicated namespace prefix", 65 | } 66 | } 67 | 68 | fn cause(&self) -> Option<&dyn std::error::Error> { 69 | match *self { 70 | Error::Io(ref e) => Some(e), 71 | Error::Utf8(ref e) => Some(e), 72 | _ => None, 73 | } 74 | } 75 | } 76 | 77 | impl From for Error { 78 | fn from(err: XmlReadError) -> Error { 79 | match *err.kind() { 80 | XmlReadErrorKind::Io(ref err) => Error::Io(io::Error::new(err.kind(), err.to_string())), 81 | XmlReadErrorKind::Utf8(ref err) => Error::Utf8(*err), 82 | XmlReadErrorKind::UnexpectedEof => Error::Io(io::Error::new(io::ErrorKind::UnexpectedEof, "Encountered unexpected eof")), 83 | XmlReadErrorKind::Syntax(ref msg) => Error::MalformedXml { 84 | msg: msg.clone(), 85 | pos: Position::from_xml_position(&err), 86 | }, 87 | } 88 | } 89 | } 90 | 91 | impl From for Error { 92 | fn from(err: XmlWriteError) -> Error { 93 | match err { 94 | XmlWriteError::Io(err) => Error::Io(err), 95 | err => Err(err).unwrap(), 96 | } 97 | } 98 | } 99 | 100 | impl From for std::io::Error { 101 | fn from(e: Error) -> Self { 102 | std::io::Error::new(std::io::ErrorKind::Other, e.to_string()) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /xml/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod children; 2 | mod element; 3 | mod error; 4 | mod namespace; 5 | mod options; 6 | mod position; 7 | mod qname; 8 | mod xml_atom; 9 | 10 | pub use children::*; 11 | pub use element::Element; 12 | pub use error::*; 13 | pub use namespace::*; 14 | pub use options::*; 15 | pub use position::*; 16 | pub use qname::*; 17 | pub use xml; 18 | pub use xml_atom::*; 19 | -------------------------------------------------------------------------------- /xml/src/namespace.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, mem}; 2 | 3 | use string_cache::Atom; 4 | 5 | use crate::{Error, XmlAtom}; 6 | 7 | #[derive(Debug, Default, Clone)] 8 | pub struct NamespaceMap { 9 | pub(crate) prefix_to_ns: BTreeMap, XmlAtom<'static>>, 10 | ns_to_prefix: BTreeMap, XmlAtom<'static>>, 11 | } 12 | 13 | impl NamespaceMap { 14 | pub fn new() -> NamespaceMap { 15 | NamespaceMap { 16 | prefix_to_ns: BTreeMap::new(), 17 | ns_to_prefix: BTreeMap::new(), 18 | } 19 | } 20 | 21 | pub fn get_prefix(&self, url: &str) -> Option<&str> { 22 | // same shit as with Element::remove_attr for the explanation. 23 | let atom = XmlAtom::Borrowed(url); 24 | let static_atom: &XmlAtom<'static> = unsafe { mem::transmute(&atom) }; 25 | self.ns_to_prefix.get(static_atom).map(|x| x.borrow()) 26 | } 27 | 28 | pub fn set_prefix(&mut self, url: &str, prefix: &str) -> Result<(), Error> { 29 | let prefix = XmlAtom::Shared(Atom::from(prefix)); 30 | if self.prefix_to_ns.contains_key(&prefix) { 31 | return Err(Error::DuplicateNamespacePrefix); 32 | } 33 | 34 | let url = XmlAtom::Shared(Atom::from(url)); 35 | if let Some(old_prefix) = self.ns_to_prefix.remove(&url) { 36 | self.prefix_to_ns.remove(&old_prefix); 37 | } 38 | 39 | self.ns_to_prefix.insert(url.clone(), prefix.clone()); 40 | self.prefix_to_ns.insert(prefix.clone(), url.clone()); 41 | 42 | Ok(()) 43 | } 44 | 45 | fn generate_prefix(&self) -> XmlAtom<'static> { 46 | let mut i = 1; 47 | loop { 48 | let random_prefix = format!("ns{}", i); 49 | if !self.prefix_to_ns.contains_key(&XmlAtom::Borrowed(&random_prefix)) { 50 | return XmlAtom::Shared(Atom::from(random_prefix)); 51 | } 52 | i += 1; 53 | } 54 | } 55 | 56 | pub fn register_if_missing(&mut self, url: &str, prefix: Option<&str>) -> bool { 57 | if self.get_prefix(url).is_some() { 58 | return false; 59 | } 60 | 61 | let stored_prefix = if let Some(prefix) = prefix { 62 | let prefix = XmlAtom::Borrowed(prefix); 63 | if self.prefix_to_ns.get(&prefix).is_some() { 64 | self.generate_prefix() 65 | } else { 66 | XmlAtom::Shared(Atom::from(prefix.borrow())) 67 | } 68 | } else { 69 | self.generate_prefix() 70 | }; 71 | 72 | let url = XmlAtom::Shared(Atom::from(url)); 73 | self.prefix_to_ns.insert(stored_prefix.clone(), url.clone()); 74 | self.ns_to_prefix.insert(url, stored_prefix); 75 | true 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /xml/src/options.rs: -------------------------------------------------------------------------------- 1 | /// Xml Prolog version handle by xmpp_xml 2 | pub enum XmlProlog { 3 | Version10, 4 | Version11, 5 | } 6 | 7 | /// A struct that define write options. 8 | pub struct WriteOptions { 9 | pub(crate) xml_prolog: Option, 10 | pub(crate) write_end_tag: bool, 11 | } 12 | 13 | impl Default for WriteOptions { 14 | fn default() -> WriteOptions { 15 | WriteOptions { 16 | xml_prolog: Some(XmlProlog::Version10), 17 | write_end_tag: true, 18 | } 19 | } 20 | } 21 | 22 | impl WriteOptions { 23 | pub fn new() -> WriteOptions { 24 | WriteOptions { ..WriteOptions::default() } 25 | } 26 | 27 | /// Define which xml prolog will be displayed when rendering an Element. 28 | /// 29 | /// Note that prolog is optional, an XML document with a missing prolog is well-formed but not valid. 30 | /// 31 | /// See RFC: [W3C XML 26 November 2008](https://www.w3.org/TR/xml/#sec-prolog-dtd) 32 | pub fn set_xml_prolog(mut self, prolog: Option) -> Self { 33 | self.xml_prolog = prolog; 34 | 35 | self 36 | } 37 | 38 | /// Define if we write the end tag of an element. 39 | pub fn set_write_end_tag(mut self, value: bool) -> Self { 40 | self.write_end_tag = value; 41 | 42 | self 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /xml/src/position.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use xml::common::Position as XmlPosition; 4 | 5 | /// Represents a position in the source. 6 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] 7 | pub struct Position { 8 | line: u64, 9 | column: u64, 10 | } 11 | 12 | impl Position { 13 | /// Creates a new position. 14 | pub fn new(line: u64, column: u64) -> Position { 15 | Position { line, column } 16 | } 17 | 18 | pub(crate) fn from_xml_position(pos: &dyn XmlPosition) -> Position { 19 | let pos = pos.position(); 20 | Position::new(pos.row, pos.column) 21 | } 22 | 23 | /// Returns the line number of the position 24 | pub fn line(&self) -> u64 { 25 | self.line 26 | } 27 | /// Returns the column of the position 28 | pub fn column(&self) -> u64 { 29 | self.column 30 | } 31 | } 32 | 33 | impl fmt::Display for Position { 34 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 35 | write!(f, "{}:{}", self.line, self.column) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /xml/src/qname.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::fmt; 3 | use std::hash::{Hash, Hasher}; 4 | use std::{borrow::Cow, cmp::Ord}; 5 | 6 | use string_cache::DefaultAtom as Atom; 7 | 8 | use xml::name::OwnedName; 9 | 10 | use crate::XmlAtom; 11 | 12 | /// A `QName` represents a qualified name. 13 | /// 14 | /// A qualified name is a tag or attribute name that has a namespace and a 15 | /// local name. If the namespace is empty no namespace is assumed. It 16 | /// can be constructed from a qualified name string with the ``from`` 17 | /// method. 18 | /// 19 | /// ## Notes on Memory Management 20 | /// 21 | /// Qualified names that are user constructed for comparison purposes 22 | /// usually have a static lifetime because they are created from static 23 | /// strings. Creating qualified names from other strings might make 24 | /// memory management harder which is why `share()` exists which moves 25 | /// the `QName` internal strings to shared storage in which the lifetime 26 | /// changes to `'static`. 27 | /// 28 | /// Common usage examples: 29 | /// 30 | /// ```no_run 31 | /// # use xmpp_xml::QName; 32 | /// let href = QName::from_name("href"); 33 | /// let a = QName::from("{http://www.w3.org/1999/xhtml}a"); 34 | /// ``` 35 | #[derive(Clone)] 36 | pub struct QName<'a> { 37 | ns: Option>, 38 | name: XmlAtom<'a>, 39 | } 40 | 41 | impl<'a> QName<'a> { 42 | /// Creates a qualified name from a given string. 43 | /// 44 | /// Two formats are supported ``{namespace}tag`` or just ``tag``. 45 | /// 46 | /// ``` 47 | /// # use xmpp_xml::QName; 48 | /// let a = QName::from("{http://www.w3.org/1999/xhtml}a"); 49 | /// ``` 50 | pub fn from(s: &'a str) -> QName<'a> { 51 | let mut ns = None; 52 | let mut name = None; 53 | if s.starts_with('{') { 54 | if let Some(index) = s.find('}') { 55 | if index > 1 { 56 | ns = Some(XmlAtom::Borrowed(&s[1..index])); 57 | } 58 | name = Some(XmlAtom::Borrowed(&s[index + 1..])); 59 | } 60 | } 61 | 62 | QName { 63 | ns, 64 | name: name.unwrap_or(XmlAtom::Borrowed(s)), 65 | } 66 | } 67 | 68 | /// Creates a qualified name from a given string without namespace. 69 | /// 70 | /// This is slightly faster than using ``from()``. 71 | pub fn from_name(name: &'a str) -> QName<'a> { 72 | QName { 73 | ns: None, 74 | name: XmlAtom::Borrowed(name), 75 | } 76 | } 77 | 78 | /// Creates a qualified name from a namespace and name. 79 | pub fn from_ns_name(ns: Option<&'a str>, name: &'a str) -> QName<'a> { 80 | QName { 81 | ns: ns.map(|x| XmlAtom::Borrowed(x)), 82 | name: XmlAtom::Borrowed(name), 83 | } 84 | } 85 | 86 | /// Returns the name portion of the qualified name. This is the local 87 | /// tag or attribute name. 88 | pub fn name(&self) -> &str { 89 | &self.name 90 | } 91 | 92 | /// Returns the optional namespace of this element. This is the URL of 93 | /// the namespace and not the prefix. The information about the latter 94 | /// is not retained. 95 | pub fn ns(&self) -> Option<&str> { 96 | self.ns.as_ref().map(|x| x.borrow()) 97 | } 98 | 99 | /// Creates a shared `QName` with static lifetime from an already 100 | /// existing `QName`. The internal strings are interned and might 101 | /// be shared with other instances. 102 | pub fn share(&self) -> QName<'static> { 103 | QName { 104 | name: XmlAtom::Shared(Atom::from(self.name.borrow())), 105 | ns: self.ns.as_ref().map(|x| XmlAtom::Shared(Atom::from(x.borrow()))), 106 | } 107 | } 108 | 109 | pub(crate) fn from_owned_name(name: OwnedName) -> QName<'static> { 110 | QName { 111 | name: XmlAtom::Shared(Atom::from(name.local_name)), 112 | ns: match name.namespace { 113 | Some(ns) => { 114 | if !ns.is_empty() { 115 | Some(XmlAtom::Shared(Atom::from(ns))) 116 | } else { 117 | None 118 | } 119 | } 120 | _ => None, 121 | }, 122 | } 123 | } 124 | } 125 | 126 | impl<'a> PartialEq for QName<'a> { 127 | fn eq(&self, other: &QName<'a>) -> bool { 128 | self.name() == other.name() && self.ns() == other.ns() 129 | } 130 | } 131 | 132 | impl<'a> fmt::Debug for QName<'a> { 133 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 134 | write!(f, "QName(\"{}\")", self) 135 | } 136 | } 137 | 138 | impl<'a> fmt::Display for QName<'a> { 139 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 140 | if let Some(ref ns) = self.ns { 141 | write!(f, "{{{}}}", ns.borrow())?; 142 | } 143 | write!(f, "{}", self.name.borrow()) 144 | } 145 | } 146 | 147 | impl<'a> Eq for QName<'a> {} 148 | 149 | impl<'a> Hash for QName<'a> { 150 | fn hash(&self, state: &mut H) { 151 | self.name.hash(state); 152 | if let Some(ref ns) = self.ns { 153 | ns.hash(state); 154 | } 155 | } 156 | } 157 | 158 | impl<'a> PartialOrd for QName<'a> { 159 | fn partial_cmp(&self, other: &QName<'a>) -> Option { 160 | self.name().partial_cmp(other.name()) 161 | } 162 | } 163 | 164 | impl<'a> Ord for QName<'a> { 165 | fn cmp(&self, other: &QName<'a>) -> Ordering { 166 | self.name().cmp(other.name()) 167 | } 168 | } 169 | 170 | /// Convenience trait to get a `QName` from an object. 171 | /// 172 | /// This is used for the accessor interface on elements. 173 | pub trait AsQName<'a> { 174 | /// Returns a Cow'ed `QName` from the given object. 175 | fn as_qname(&self) -> Cow<'a, QName<'a>>; 176 | } 177 | 178 | impl<'a> AsQName<'a> for &'a QName<'a> { 179 | #[inline(always)] 180 | fn as_qname(&self) -> Cow<'a, QName<'a>> { 181 | Cow::Borrowed(self) 182 | } 183 | } 184 | 185 | impl<'a> AsQName<'a> for &'a str { 186 | #[inline(always)] 187 | fn as_qname(&self) -> Cow<'a, QName<'a>> { 188 | Cow::Owned(QName::from(self)) 189 | } 190 | } 191 | 192 | impl<'a> AsQName<'a> for (&'a str, &'a str) { 193 | #[inline(always)] 194 | fn as_qname(&self) -> Cow<'a, QName<'a>> { 195 | Cow::Owned(QName::from_ns_name(Some(self.0), self.1)) 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /xml/src/xml_atom.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp::Ordering, fmt, ops::Deref}; 2 | 3 | use string_cache::DefaultAtom; 4 | 5 | pub enum XmlAtom<'a> { 6 | Shared(DefaultAtom), 7 | Borrowed(&'a str), 8 | } 9 | 10 | impl<'a> Deref for XmlAtom<'a> { 11 | type Target = str; 12 | 13 | #[inline(always)] 14 | fn deref(&self) -> &str { 15 | match *self { 16 | XmlAtom::Shared(ref atom) => atom.deref(), 17 | XmlAtom::Borrowed(s) => s, 18 | } 19 | } 20 | } 21 | 22 | impl<'a> XmlAtom<'a> { 23 | #[inline(always)] 24 | pub fn borrow(&self) -> &str { 25 | &self 26 | } 27 | } 28 | 29 | impl<'a> fmt::Debug for XmlAtom<'a> { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | write!(f, "{:?}", self.borrow()) 32 | } 33 | } 34 | 35 | impl<'a> Clone for XmlAtom<'a> { 36 | fn clone(&self) -> XmlAtom<'a> { 37 | XmlAtom::Shared(DefaultAtom::from(self.borrow())) 38 | } 39 | } 40 | 41 | impl<'a> PartialEq for XmlAtom<'a> { 42 | fn eq(&self, other: &XmlAtom<'a>) -> bool { 43 | self.borrow().eq(other.borrow()) 44 | } 45 | } 46 | 47 | impl<'a> Eq for XmlAtom<'a> {} 48 | 49 | impl<'a> PartialOrd for XmlAtom<'a> { 50 | fn partial_cmp(&self, other: &XmlAtom<'a>) -> Option { 51 | self.borrow().partial_cmp(other.borrow()) 52 | } 53 | } 54 | 55 | impl<'a> Ord for XmlAtom<'a> { 56 | fn cmp(&self, other: &XmlAtom<'a>) -> Ordering { 57 | self.borrow().cmp(other.borrow()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /xml/tests/test_basic.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | use xmpp_xml::{Element, QName, WriteOptions, XmlProlog}; 3 | 4 | #[test] 5 | fn test_basics() { 6 | let root = Element::from_reader( 7 | r#" 8 | 9 | 10 | Item 1Tail 1 11 | Item 2Tail 2 12 | Item 3Tail 3 13 | 14 | 15 | "# 16 | .as_bytes(), 17 | ) 18 | .unwrap(); 19 | 20 | let list = root.find("list").unwrap(); 21 | assert_eq!(list.tag(), &QName::from("list")); 22 | 23 | let items: Vec<_> = list.children().map(|x| x.text()).collect(); 24 | assert_eq!(items.as_slice(), &["Item 1", "Item 2", "Item 3"]); 25 | 26 | let tails: Vec<_> = list.children().map(|x| x.tail().trim()).collect(); 27 | assert_eq!(tails.as_slice(), &["Tail 1", "Tail 2", "Tail 3"]); 28 | } 29 | 30 | #[test] 31 | fn test_attributes() { 32 | let root = Element::from_reader( 33 | r#" 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | "# 42 | .as_bytes(), 43 | ) 44 | .unwrap(); 45 | 46 | let list = root.find("list").unwrap(); 47 | assert_eq!(list.tag(), &QName::from("list")); 48 | 49 | let items: Vec<_> = list.children().map(|x| x.get_attr("attr").unwrap_or("")).collect(); 50 | assert_eq!(items.as_slice(), &["foo1", "foo2", "foo3"]); 51 | 52 | let mut attrs: Vec<_> = list.attrs().map(|(k, v)| format!("{}={}", k, v)).collect(); 53 | attrs.sort(); 54 | assert_eq!(attrs.iter().map(|x| x.as_str()).collect::>(), vec!["a=1", "b=2", "c=3"]); 55 | 56 | assert_eq!(list.attr_count(), 3); 57 | } 58 | 59 | #[test] 60 | fn test_namespaces() { 61 | let root = Element::from_reader( 62 | r#" 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | "# 71 | .as_bytes(), 72 | ) 73 | .unwrap(); 74 | 75 | let list = root.find("{root}list").unwrap(); 76 | assert_eq!(list.tag(), &QName::from("{root}list")); 77 | 78 | let items: Vec<_> = list.children().map(|x| x.get_attr("{child}attr").unwrap_or("")).collect(); 79 | assert_eq!(items.as_slice(), &["foo1", "foo2", "foo3"]); 80 | 81 | let mut attrs: Vec<_> = list.attrs().map(|(k, v)| format!("{}={}", k, v)).collect(); 82 | attrs.sort(); 83 | assert_eq!(attrs.iter().map(|x| x.as_str()).collect::>(), vec!["a=1", "b=2", "c=3"]); 84 | 85 | assert_eq!(list.attr_count(), 3); 86 | 87 | assert_eq!(root.get_namespace_prefix("http://www.w3.org/2000/xmlns/"), Some("xmlns")); 88 | 89 | assert_eq!(root.get_namespace_prefix("child"), Some("foo")); 90 | assert_eq!(root.get_namespace_prefix("root"), Some("")); 91 | assert_eq!(root.get_namespace_prefix("missing"), None); 92 | 93 | assert_eq!(list.get_namespace_prefix("child"), Some("foo")); 94 | assert_eq!(list.get_namespace_prefix("root"), Some("")); 95 | assert_eq!(list.get_namespace_prefix("missing"), None); 96 | } 97 | 98 | #[test] 99 | fn test_entities() { 100 | let root = Element::from_reader( 101 | r#" 102 | S 103 | "# 104 | .as_bytes(), 105 | ) 106 | .unwrap(); 107 | 108 | assert_eq!(root.text(), "S"); 109 | } 110 | 111 | #[test] 112 | fn test_write_stuff() { 113 | let mut root = Element::new(&QName::from("{myns}root")); 114 | root.set_namespace_prefix("myns", "x").unwrap(); 115 | let mut out: Vec = Vec::new(); 116 | root.to_writer(&mut out).unwrap(); 117 | let out = String::from_utf8(out).unwrap(); 118 | assert_eq!(&out, ""); 119 | } 120 | 121 | #[test] 122 | fn test_basic_creation() { 123 | let mut root = Element::new("{demo}mydoc"); 124 | root.set_namespace_prefix("demo", "").unwrap(); 125 | 126 | let mut list = Element::new_with_namespaces("{demo}list", &root); 127 | 128 | for x in 0..3 { 129 | let mut child = Element::new_with_namespaces("{demo}item", &root); 130 | child.set_text(format!("Item {}", x)); 131 | list.append_child(child); 132 | } 133 | 134 | root.append_child(list); 135 | assert_eq!( 136 | &root.to_string().unwrap(), 137 | "\ 138 | \ 139 | \ 140 | \ 141 | Item 0\ 142 | Item 1\ 143 | Item 2\ 144 | \ 145 | " 146 | ); 147 | } 148 | 149 | #[test] 150 | fn test_alternative_creation() { 151 | let mut root = Element::new("{demo}mydoc"); 152 | root.set_namespace_prefix("demo", "").unwrap(); 153 | 154 | { 155 | let mut list = root.append_new_child("{demo}list"); 156 | for x in 0..3 { 157 | let mut child = list.append_new_child("{demo}item"); 158 | child.set_text(format!("Item {}", x)); 159 | } 160 | } 161 | 162 | assert_eq!( 163 | &root.to_string().unwrap(), 164 | "\ 165 | \ 166 | \ 167 | \ 168 | Item 0\ 169 | Item 1\ 170 | Item 2\ 171 | \ 172 | " 173 | ); 174 | } 175 | 176 | #[test] 177 | fn test_whitespace() { 178 | let root = Element::from_reader( 179 | r#" 180 | 181 | 182 | Item 1 Tail 1 183 | Item 2 Tail 2 184 | Item 3 Tail 3 185 | 186 | 187 | "# 188 | .as_bytes(), 189 | ) 190 | .unwrap(); 191 | 192 | assert_eq!(root.text(), "\n "); 193 | 194 | let list = root.find("list").unwrap(); 195 | assert_eq!(list.tag(), &QName::from("list")); 196 | 197 | let items: Vec<_> = list.children().map(|x| x.text()).collect(); 198 | assert_eq!(items.as_slice(), &[" Item 1 ", " Item 2 ", " Item 3 "]); 199 | } 200 | 201 | #[test] 202 | fn test_creation_without_xml_declaration() { 203 | let mut root = Element::new("{demo}mydoc"); 204 | root.set_namespace_prefix("demo", "").unwrap(); 205 | { 206 | let mut list = root.append_new_child("{demo}list"); 207 | for x in 0..3 { 208 | let mut child = list.append_new_child("{demo}item"); 209 | child.set_text(format!("Item {}", x)); 210 | } 211 | } 212 | 213 | let mut out: Vec = Vec::new(); 214 | let options = WriteOptions::new().set_xml_prolog(None); 215 | 216 | root.to_writer_with_options(&mut out, options).unwrap(); 217 | assert_eq!( 218 | str::from_utf8(&out).unwrap(), 219 | "\ 220 | \ 221 | \ 222 | Item 0\ 223 | Item 1\ 224 | Item 2\ 225 | \ 226 | " 227 | ); 228 | } 229 | 230 | #[test] 231 | fn test_creation_with_xml_prolog_10() { 232 | let mut root = Element::new("{demo}mydoc"); 233 | root.set_namespace_prefix("demo", "").unwrap(); 234 | { 235 | let mut list = root.append_new_child("{demo}list"); 236 | for x in 0..3 { 237 | let mut child = list.append_new_child("{demo}item"); 238 | child.set_text(format!("Item {}", x)); 239 | } 240 | } 241 | 242 | let mut out: Vec = Vec::new(); 243 | let options = WriteOptions::new().set_xml_prolog(Some(XmlProlog::Version10)); 244 | 245 | root.to_writer_with_options(&mut out, options).unwrap(); 246 | assert_eq!( 247 | str::from_utf8(&out).unwrap(), 248 | "\ 249 | \ 250 | \ 251 | \ 252 | Item 0\ 253 | Item 1\ 254 | Item 2\ 255 | \ 256 | " 257 | ); 258 | } 259 | 260 | #[test] 261 | fn test_creation_with_xml_prolog_11() { 262 | let mut root = Element::new("{demo}mydoc"); 263 | root.set_namespace_prefix("demo", "").unwrap(); 264 | { 265 | let mut list = root.append_new_child("{demo}list"); 266 | for x in 0..3 { 267 | let mut child = list.append_new_child("{demo}item"); 268 | child.set_text(format!("Item {}", x)); 269 | } 270 | } 271 | 272 | let mut out: Vec = Vec::new(); 273 | let options = WriteOptions::new().set_xml_prolog(Some(XmlProlog::Version11)); 274 | 275 | root.to_writer_with_options(&mut out, options).unwrap(); 276 | assert_eq!( 277 | str::from_utf8(&out).unwrap(), 278 | "\ 279 | \ 280 | \ 281 | \ 282 | Item 0\ 283 | Item 1\ 284 | Item 2\ 285 | \ 286 | " 287 | ); 288 | } 289 | 290 | #[test] 291 | fn test_creation_with_no_xml_prolog_defined() { 292 | let mut root = Element::new("{demo}mydoc"); 293 | root.set_namespace_prefix("demo", "").unwrap(); 294 | { 295 | let mut list = root.append_new_child("{demo}list"); 296 | for x in 0..3 { 297 | let mut child = list.append_new_child("{demo}item"); 298 | child.set_text(format!("Item {}", x)); 299 | } 300 | } 301 | 302 | let mut out: Vec = Vec::new(); 303 | let options = WriteOptions::new(); 304 | 305 | root.to_writer_with_options(&mut out, options).unwrap(); 306 | assert_eq!( 307 | str::from_utf8(&out).unwrap(), 308 | "\ 309 | \ 310 | \ 311 | \ 312 | Item 0\ 313 | Item 1\ 314 | Item 2\ 315 | \ 316 | " 317 | ); 318 | } 319 | 320 | #[test] 321 | fn test_render_multiple_times() { 322 | let mut root = Element::new("{demo}mydoc"); 323 | root.set_namespace_prefix("demo", "").unwrap(); 324 | root.set_attr(("demo", "id"), "some_id".to_string()) 325 | .set_attr(("demo", "name"), "some_name".to_string()) 326 | .set_attr(("demo", "some-other-attr"), "other_attr".to_string()); 327 | 328 | let mut out: Vec = Vec::new(); 329 | 330 | root.to_writer_with_options(&mut out, WriteOptions::new()).unwrap(); 331 | assert_eq!( 332 | str::from_utf8(&out).unwrap(), 333 | "\ 334 | \ 335 | " 336 | ); 337 | 338 | let mut out2: Vec = Vec::new(); 339 | root.to_writer_with_options(&mut out2, WriteOptions::new()).unwrap(); 340 | assert_eq!( 341 | str::from_utf8(&out2).unwrap(), 342 | "\ 343 | \ 344 | " 345 | ); 346 | } 347 | 348 | #[test] 349 | fn test_mut_finding() { 350 | let mut root = Element::from_reader( 351 | r#" 352 | 353 | 354 | Item 1 Tail 1 355 | Item 2 Tail 2 356 | Item 3 Tail 3 357 | 358 | 359 | "# 360 | .as_bytes(), 361 | ) 362 | .unwrap(); 363 | 364 | { 365 | let mut list = root.find_mut("list").unwrap(); 366 | for item in list.find_all_mut("item") { 367 | item.set_text("wat"); 368 | } 369 | } 370 | 371 | let v: Vec<_> = root.find("list").unwrap().find_all("item").map(|x| x.text()).collect(); 372 | assert_eq!(&v, &["wat", "wat", "wat"]); 373 | } 374 | --------------------------------------------------------------------------------