├── .gitignore ├── .woodpecker ├── dco.yml ├── integration.yml ├── test.yml └── validate.yml ├── CHANGELOG.md ├── CONTRIBUTORS ├── DCO ├── LICENSE ├── LICENSE-GO ├── Makefile ├── README.md ├── bin ├── bob.go ├── bob_test.go └── disco.go ├── bind.go ├── bind_test.go ├── blocklist ├── blocking.go ├── blocking_test.go ├── handler.go ├── integration_test.go └── mod_integration_report.lua ├── bookmarks ├── channel.go ├── channel_test.go ├── disco.go ├── doc.go ├── handler.go ├── integration_test.go ├── iter.go ├── publish.go └── publish_test.go ├── carbons ├── carbons.go ├── carbons_test.go ├── disco.go ├── handler.go └── handler_test.go ├── color ├── .gitignore ├── color.go ├── color_test.go ├── cvd_string.go └── example_test.go ├── commands ├── actions.go ├── actions_string.go ├── actions_test.go ├── commands.go ├── disco.go ├── iter.go ├── notes.go ├── notes_test.go ├── notetype_string.go ├── response.go └── response_test.go ├── component ├── component.go ├── component_test.go └── integration_test.go ├── conn.go ├── crypto ├── crypto.go ├── crypto_test.go ├── disco.go ├── handler.go ├── handler_test.go ├── trustmsg.go ├── trustmsg_test.go └── trustmsgexport_test.go ├── delay ├── delay.go └── delay_test.go ├── design ├── 110_mam.md ├── 171_caps.md ├── 18_iqmux.md ├── 19_ibb.md ├── 25_stanzamux.md ├── 266_crypto.md ├── 28_disco.md ├── 292_pubsub_notify.md ├── 42_integration_testing.md ├── README.md └── template.md ├── dial ├── dial.go └── integration_test.go ├── disco ├── caps.go ├── caps_test.go ├── categories.go ├── disco.go ├── features.go ├── gen.go ├── handler.go ├── handler_test.go ├── info.go ├── info │ ├── feature.go │ ├── feature_test.go │ ├── identity.go │ └── identity_test.go ├── info_test.go ├── integration_test.go ├── items.go ├── items │ ├── items.go │ └── items_test.go └── items_test.go ├── doc.go ├── docs ├── ARCHITECTURE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── SECURITY.md ├── SUPPORT.md ├── dial.gv ├── extensions.md ├── overview.md ├── release.md └── xeps.md ├── echobot_example_test.go ├── error_test.go ├── examples ├── commands │ ├── .gitignore │ └── commands.go ├── echobot │ ├── .gitignore │ ├── echo.go │ └── main.go ├── go.mod ├── go.sum ├── im │ ├── .gitignore │ └── main.go └── msgrepl │ ├── .gitignore │ └── main.go ├── export_test.go ├── features.go ├── file ├── metadata.go └── metadata_test.go ├── form ├── disco.go ├── doc.go ├── fields.go ├── form.go ├── form_test.go ├── iter.go └── options.go ├── forward ├── disco.go ├── forward.go └── forward_test.go ├── go.mod ├── go.sum ├── handler.go ├── handler_test.go ├── history ├── doc.go ├── fin.go ├── fin_test.go ├── history.go ├── history_test.go ├── integration_test.go ├── iter.go ├── query.go └── query_test.go ├── ibb ├── aioxmpp_integration_test.py ├── conn.go ├── ibb.go ├── ibb_internal_test.go ├── ibb_test.go ├── integration_test.go ├── listen.go ├── payloads.go └── slixmpp_integration_test.py ├── internal ├── attr │ ├── attr.go │ ├── attr_test.go │ ├── doc.go │ ├── idgen.go │ └── idgen_test.go ├── cid │ ├── cid.go │ └── cid_test.go ├── decl │ ├── decl.go │ └── decl_test.go ├── discover │ ├── lookup.go │ ├── lookup_integration_test.go │ └── lookup_test.go ├── doc.go ├── genfeature │ └── gen.go ├── genform │ └── gen.go ├── genpubsub │ └── gen.go ├── integration │ ├── aioxmpp │ │ ├── aioxmpp.go │ │ └── aioxmpp_client.py │ ├── ejabberd │ │ ├── config.go │ │ └── ejabberd.go │ ├── integration.go │ ├── jackal │ │ ├── config.go │ │ ├── config.yml.tmpl │ │ └── jackal.go │ ├── mcabber │ │ ├── config.go │ │ └── mcabber.go │ ├── mellium │ │ ├── config.go │ │ └── mellium.go │ ├── prosody │ │ ├── LICENSE.modules │ │ ├── config.go │ │ ├── mod_muc_defaults.go │ │ ├── mod_muc_defaults.lua │ │ ├── mod_trustall.go │ │ ├── mod_trustall.lua │ │ ├── prosody.cfg.lua.tmpl │ │ └── prosody.go │ ├── python │ │ ├── config.go │ │ ├── python.go │ │ ├── python_config.ini │ │ └── xmpptest.py │ ├── sendxmpp │ │ ├── config.go │ │ └── sendxmpp.go │ ├── slixmpp │ │ ├── slixmpp.go │ │ └── slixmpp_client.py │ └── waitdial.go ├── marshal │ ├── encode.go │ └── encode_test.go ├── ns │ └── ns.go ├── saslerr │ ├── condition_string.go │ ├── condition_test.go │ ├── errors.go │ └── errors_test.go ├── stream │ ├── reader.go │ ├── reader_test.go │ ├── stream.go │ └── stream_test.go ├── wskey │ └── key.go └── xmpptest │ ├── features.go │ ├── marshal.go │ ├── marshal_test.go │ ├── session.go │ ├── session_test.go │ ├── tokens.go │ ├── tokens_test.go │ └── transformers.go ├── jid ├── benchmark_test.go ├── disco.go ├── doc.go ├── escape.go ├── escape_test.go ├── fuzz_test.go ├── jid.go ├── jid_test.go ├── unsafe.go └── unsafe_test.go ├── muc ├── affiliation_string.go ├── disco.go ├── integration_test.go ├── invites.go ├── invites_test.go ├── muc.go ├── muc_test.go ├── options.go ├── options_test.go ├── room.go ├── room_integration_test.go ├── room_test.go ├── types.go └── types_test.go ├── mux ├── mux.go ├── mux_test.go ├── option.go └── stanza.go ├── negotiator.go ├── oob ├── disco.go ├── oob.go └── oob_test.go ├── paging ├── disco.go ├── rsm.go ├── rsm_test.go └── types.go ├── ping ├── aioxmpp_integration_test.py ├── disco.go ├── integration_test.go ├── ping.go ├── ping_test.go └── slixmpp_integration_test.py ├── pubsub ├── conditions.go ├── config_integration_test.go ├── configure.go ├── create.go ├── doc.go ├── fetch.go ├── integration_test.go ├── pubsub.go ├── retract.go └── string.go ├── receipts ├── disco.go ├── receipts.go └── receipts_test.go ├── roster ├── integration_test.go ├── roster.go ├── roster_test.go └── ver.go ├── s2s ├── bidi.go ├── bidi_test.go └── doc.go ├── sasl.go ├── sasl_integration_test.go ├── sasl_test.go ├── send_test.go ├── session.go ├── session_iq.go ├── session_iq_test.go ├── session_message.go ├── session_message_test.go ├── session_presence.go ├── session_presence_test.go ├── session_test.go ├── sessionstate_string.go ├── stanza ├── delay.go ├── delay_test.go ├── doc.go ├── error.go ├── error_test.go ├── example_error_test.go ├── example_pingstream_test.go ├── example_pingstruct_test.go ├── id_test.go ├── iq.go ├── iq_test.go ├── message.go ├── message_test.go ├── presence.go ├── presence_test.go ├── stanza.go └── stanza_test.go ├── starttls.go ├── starttls_test.go ├── stream ├── benchmark_test.go ├── doc.go ├── error.go ├── error_test.go ├── stream.go ├── version.go └── version_test.go ├── styling ├── README.md ├── disable.go ├── disable_test.go ├── disco.go ├── example_test.go ├── export_test.go ├── fuzz_test.go ├── style_string.go ├── styling.go └── styling_test.go ├── tools.go ├── upload ├── disco.go ├── doc.go ├── integration_test.go ├── upload.go └── upload_test.go ├── uri ├── export_test.go ├── iri.go └── iri_test.go ├── version ├── disco.go ├── version.go └── version_test.go ├── websocket ├── doc.go ├── integration_test.go ├── negotiator.go └── ws.go ├── x509 ├── x509.go └── x509_test.go └── xtime ├── disco.go ├── integration_test.go ├── time.go ├── time_example_test.go └── time_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | decoder_tests.json 2 | .env 3 | *.html 4 | new.txt 5 | old.txt 6 | *.out 7 | *.svg 8 | *.test 9 | vendor/ 10 | *.xml 11 | -------------------------------------------------------------------------------- /.woodpecker/dco.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | dco: 3 | image: alpine 4 | commands: 5 | - apk add git 6 | - git version 7 | - git fetch --no-tags origin +refs/heads/main 8 | - | 9 | set -e 10 | function on_err { 11 | [ $? -eq 0 ] && exit 12 | cat <> /etc/apk/repositories << EOF; $$(echo) 25 | http://dl-cdn.alpinelinux.org/alpine/edge/testing 26 | 27 | EOF 28 | - apk update 29 | - apk add go python3 lua5.2 py3-slixmpp py3-aioxmpp openssl prosody lua-unbound jackal 30 | - go test -v -tags "integration" -run Integration ./... 31 | depends_on: 32 | - dco 33 | - test 34 | -------------------------------------------------------------------------------- /.woodpecker/test.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | info: &info 3 | image: golang:1.22-alpine 4 | environment: 5 | CGO_ENABLED: "1" 6 | commands: 7 | - go version 8 | - go env 9 | when: 10 | - event: [tag, push, pull_request, release, manual] 11 | test: 12 | << : *info 13 | commands: 14 | - apk add gcc musl-dev 15 | - go test -race -v -cover ./... 16 | examples: 17 | << : *info 18 | environment: 19 | CGO_ENABLED: "0" 20 | directory: examples/ 21 | commands: | 22 | for d in ./*/; do 23 | cd $d 24 | go build 25 | cd .. 26 | done 27 | 28 | depends_on: 29 | - dco 30 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | // This is the official list of contributors for copyright purposes. 2 | // 3 | // If you see your name twice, please fix your commits or create a .mailmap 4 | // entry for yourself and regenerate this file by running make CONTRIBUTORS. 5 | // For more info see https://www.git-scm.com/docs/git-check-mailmap 6 | 7 | Aleksandr Yakimenko 8 | Ashish SHUKLA 9 | Dane David 10 | Jordan 11 | Julian Huhn 12 | Julien Lavocat 13 | Lennart Jablonka 14 | Michael Vetter 15 | Oleg Maximenko 16 | Sam Whited 17 | Steffen Jaeckel 18 | TAKI MEKHALFA 19 | cemturker 20 | genofire 21 | sxvghd 22 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2014 The Mellium Contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /LICENSE-GO: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XMPP 2 | 3 | [![GoDoc](https://godoc.org/mellium.im/xmpp?status.svg)][docs] 4 | [![Chat](https://img.shields.io/badge/XMPP-users@mellium.chat-orange.svg)](https://mellium.chat) 5 | [![License](https://img.shields.io/badge/license-FreeBSD-blue.svg)](https://opensource.org/licenses/BSD-2-Clause) 6 | [![Build Status](https://ci.codeberg.org/api/badges/mellium/xmpp/status.svg)](https://ci.codeberg.org/mellium/xmpp) 7 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6086/badge)](https://bestpractices.coreinfrastructure.org/projects/6086) 8 | 9 | 10 | 11 | 12 | An Extensible Messaging and Presence Protocol (XMPP) library in Go. 13 | XMPP (sometimes known as "Jabber") is a protocol for near-real-time data 14 | transmission, most commonly used for instant messaging, video chat signaling, 15 | and related functionality. 16 | This library aims to provide general protocol support with additional packages 17 | that focus on modern instant messaging use cases. 18 | 19 | This library supports instant messaging features such as: 20 | 21 | - Individual and group chat, 22 | - Blocking and unblocking users, 23 | - Forms and commands (eg. for controlling bots and gateways), 24 | - Retrieving message history, 25 | - General publish-subscribe semantics for storing state and data, 26 | - Parsing simple text styling (eg. bold, italic, quotes, etc.), 27 | - and more! 28 | 29 | To use it in your project, import it (or any of its other packages) like so: 30 | 31 | ```go 32 | import mellium.im/xmpp 33 | ``` 34 | 35 | If you're looking to get started and need some help, see the [API docs][docs] or 36 | look in the `examples/` tree for several simple usage examples. 37 | 38 | If you'd like to contribute to the project, see [CONTRIBUTING.md]. 39 | 40 | 41 | ## License 42 | 43 | The package may be used under the terms of the BSD 2-Clause License a copy of 44 | which may be found in the file "[LICENSE]". 45 | Some code in this package has been copied from Go and is used under the terms of 46 | Go's modified BSD license, a copy of which can be found in the [LICENSE-GO] 47 | file. 48 | 49 | Unless you explicitly state otherwise, any contribution submitted for inclusion 50 | in the work by you shall be licensed as above, without any additional terms or 51 | conditions. 52 | 53 | 54 | [docs]: https://pkg.go.dev/mellium.im/xmpp 55 | [CONTRIBUTING.md]: https://mellium.im/docs/CONTRIBUTING 56 | [LICENSE]: https://codeberg.org/mellium/xmpp/src/branch/main/LICENSE 57 | [LICENSE-GO]: https://codeberg.org/mellium/xmpp/src/branch/main/LICENSE-GO 58 | -------------------------------------------------------------------------------- /bin/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature -receiver Handler"; DO NOT EDIT. 2 | 3 | package bin 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | 14 | // ForFeatures implements info.FeatureIter. 15 | func (Handler) ForFeatures(node string, f func(info.Feature) error) error { 16 | if node != "" { 17 | return nil 18 | } 19 | var err error 20 | err = f(Feature) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /blocklist/mod_integration_report.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2022 The Mellium Contributors. 2 | -- Use of this source code is governed by the BSD 2-clause 3 | -- license that can be found in the LICENSE file. 4 | 5 | module:depends("blocklist"); 6 | 7 | local storage = module:open_store(); 8 | 9 | local st = require"util.stanza"; 10 | 11 | module:add_feature("urn:mellium:integration"); 12 | 13 | module:hook("iq-set/self/urn:xmpp:blocking:block", function (event) 14 | for item in event.stanza.tags[1]:childtags("item") do 15 | local report = item:get_child("report", "urn:xmpp:reporting:1"); 16 | if report then 17 | local text = report:get_child_text("text") or ""; 18 | local jid = item.attr.jid; 19 | module:log("info", "--report: [%s, %s, %s]", jid, report.attr.reason, text); 20 | local reports = storage:get("reports") or {}; 21 | reports[jid] = { 22 | jid = jid, 23 | reason = report.attr.reason, 24 | text = text, 25 | } 26 | storage:set("reports", reports) 27 | end 28 | end 29 | end, 1); 30 | 31 | module:hook("iq-get/self/urn:mellium:integration:report", function (event) 32 | local origin, stanza = event.origin, event.stanza; 33 | local reply = st.reply(stanza):tag("report", { xmlns = "urn:mellium:integration:report" }); 34 | local reports = storage:get("reports") 35 | for jid, v in pairs(reports) do 36 | reply:tag("item", v):up(); 37 | end 38 | origin.send(reply) 39 | end, -1); 40 | -------------------------------------------------------------------------------- /bookmarks/channel_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package bookmarks_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "mellium.im/xmpp/bookmarks" 11 | "mellium.im/xmpp/internal/xmpptest" 12 | ) 13 | 14 | var marshalTestCases = []xmpptest.EncodingTestCase{ 15 | 0: { 16 | Value: &bookmarks.Channel{}, 17 | XML: ``, 18 | }, 19 | 1: { 20 | NoMarshal: true, 21 | Value: &bookmarks.Channel{Autojoin: true}, 22 | XML: ``, 23 | }, 24 | 2: { 25 | Value: &bookmarks.Channel{ 26 | Autojoin: true, 27 | Name: "name", 28 | Nick: "nick", 29 | Password: "pass", 30 | Extensions: []byte("ext"), 31 | }, 32 | XML: `nickpassext`, 33 | }, 34 | 3: { 35 | Value: &bookmarks.Channel{ 36 | Autojoin: true, 37 | Name: "name", 38 | Nick: "nick", 39 | Password: "pass", 40 | Extensions: []byte("ext"), 41 | }, 42 | XML: `nickpassext`, 43 | }, 44 | } 45 | 46 | func TestEncode(t *testing.T) { 47 | xmpptest.RunEncodingTests(t, marshalTestCases) 48 | } 49 | -------------------------------------------------------------------------------- /bookmarks/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature -vars Feature:NS,FeatureNotify:NSNotify"; DO NOT EDIT. 2 | 3 | package bookmarks 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | FeatureNotify = info.Feature{Var: NSNotify} 13 | ) 14 | -------------------------------------------------------------------------------- /bookmarks/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:generate go run ../internal/genfeature -vars "Feature:NS,FeatureNotify:NSNotify" 6 | 7 | // Package bookmarks implements storing bookmarks to chat rooms. 8 | package bookmarks // import "mellium.im/xmpp/bookmarks" 9 | 10 | // Namespaces used by this package. 11 | const ( 12 | NS = "urn:xmpp:bookmarks:1" 13 | NSNotify = "urn:xmpp:bookmarks:1+notify" 14 | NSCompat = "urn:xmpp:bookmarks:1#compat" 15 | ) 16 | -------------------------------------------------------------------------------- /bookmarks/handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package bookmarks 6 | 7 | import ( 8 | "mellium.im/xmpp/disco/info" 9 | ) 10 | 11 | // Handler can be registered against a mux to handle bookmark pushes. 12 | type Handler struct { 13 | } 14 | 15 | // ForFeatures implements info.FeatureIter. 16 | func (h Handler) ForFeatures(node string, f func(info.Feature) error) error { 17 | if node != "" { 18 | return nil 19 | } 20 | 21 | err := f(FeatureNotify) 22 | if err != nil { 23 | return err 24 | } 25 | return f(Feature) 26 | } 27 | -------------------------------------------------------------------------------- /bookmarks/iter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package bookmarks 6 | 7 | import ( 8 | "context" 9 | "encoding/xml" 10 | 11 | "mellium.im/xmpp" 12 | "mellium.im/xmpp/jid" 13 | "mellium.im/xmpp/pubsub" 14 | "mellium.im/xmpp/stanza" 15 | ) 16 | 17 | // Fetch requests all bookmarks from the server and returns an iterator over the 18 | // results (blocking until the response is received and the iterator is fully 19 | // consumed or closed). 20 | func Fetch(ctx context.Context, s *xmpp.Session) *Iter { 21 | return FetchIQ(ctx, stanza.IQ{}, s) 22 | } 23 | 24 | // FetchIQ is like Fetch but it allows you to customize the IQ. 25 | // Changing the type of the provided IQ has no effect. 26 | func FetchIQ(ctx context.Context, iq stanza.IQ, s *xmpp.Session) *Iter { 27 | iq.Type = stanza.GetIQ 28 | iter := pubsub.FetchIQ(ctx, iq, s, pubsub.Query{ 29 | Node: NS, 30 | }) 31 | return &Iter{ 32 | iter: iter, 33 | } 34 | } 35 | 36 | // Iter is an iterator over bookmarks. 37 | type Iter struct { 38 | iter *pubsub.Iter 39 | current Channel 40 | err error 41 | } 42 | 43 | // Next returns true if there are more items to decode. 44 | func (i *Iter) Next() bool { 45 | if i.err != nil || !i.iter.Next() { 46 | return false 47 | } 48 | id, r := i.iter.Item() 49 | var bookmark Channel 50 | i.err = xml.NewTokenDecoder(r).Decode(&bookmark) 51 | if i.err != nil { 52 | return false 53 | } 54 | j, err := jid.Parse(id) 55 | if err != nil { 56 | return false 57 | } 58 | i.current = bookmark 59 | i.current.JID = j 60 | return true 61 | } 62 | 63 | // Err returns the last error encountered by the iterator (if any). 64 | func (i *Iter) Err() error { 65 | if i.err != nil { 66 | return i.err 67 | } 68 | 69 | return i.iter.Err() 70 | } 71 | 72 | // Bookmark returns the last bookmark parsed by the iterator. 73 | func (i *Iter) Bookmark() Channel { 74 | return i.current 75 | } 76 | 77 | // Close indicates that we are finished with the given iterator and processing 78 | // the stream may continue. 79 | // Calling it multiple times has no effect. 80 | func (i *Iter) Close() error { 81 | if i.iter == nil { 82 | return nil 83 | } 84 | return i.iter.Close() 85 | } 86 | -------------------------------------------------------------------------------- /bookmarks/publish.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package bookmarks 6 | 7 | import ( 8 | "context" 9 | 10 | "mellium.im/xmpp" 11 | "mellium.im/xmpp/jid" 12 | "mellium.im/xmpp/pubsub" 13 | "mellium.im/xmpp/stanza" 14 | ) 15 | 16 | // Publish creates or updates the bookmark. 17 | func Publish(ctx context.Context, s *xmpp.Session, b Channel) error { 18 | return PublishIQ(ctx, s, stanza.IQ{}, b) 19 | } 20 | 21 | // PublishIQ is like Publish except that it allows modifying the IQ. 22 | // Changes to the IQ type will have no effect. 23 | func PublishIQ(ctx context.Context, s *xmpp.Session, iq stanza.IQ, b Channel) error { 24 | iq.Type = stanza.SetIQ 25 | _, err := pubsub.PublishIQ(ctx, s, iq, NS, b.JID.String(), b.TokenReader()) 26 | return err 27 | } 28 | 29 | // Delete removes the bookmark. 30 | func Delete(ctx context.Context, s *xmpp.Session, b jid.JID) error { 31 | return DeleteIQ(ctx, s, stanza.IQ{}, b) 32 | } 33 | 34 | // DeleteIQ is like Delete except that it allows modifying the IQ. 35 | // Changes to the IQ type will have no effect. 36 | func DeleteIQ(ctx context.Context, s *xmpp.Session, iq stanza.IQ, b jid.JID) error { 37 | return pubsub.DeleteIQ(ctx, s, iq, NS, b.String(), true) 38 | } 39 | -------------------------------------------------------------------------------- /bookmarks/publish_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package bookmarks_test 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "encoding/xml" 11 | "errors" 12 | "testing" 13 | 14 | "mellium.im/xmlstream" 15 | "mellium.im/xmpp/bookmarks" 16 | "mellium.im/xmpp/internal/xmpptest" 17 | "mellium.im/xmpp/stanza" 18 | ) 19 | 20 | func TestDelete(t *testing.T) { 21 | var buf bytes.Buffer 22 | e := xml.NewEncoder(&buf) 23 | s := xmpptest.NewClientServer(xmpptest.ServerHandlerFunc(func(r xmlstream.TokenReadEncoder, start *xml.StartElement) error { 24 | err := e.EncodeToken(*start) 25 | if err != nil { 26 | return err 27 | } 28 | _, err = xmlstream.Copy(e, r) 29 | if err != nil { 30 | return err 31 | } 32 | return e.Flush() 33 | })) 34 | err := bookmarks.DeleteIQ(context.Background(), s.Client, stanza.IQ{ 35 | ID: "123", 36 | }, s.Server.LocalAddr()) 37 | if !errors.Is(err, stanza.Error{Condition: stanza.ServiceUnavailable}) { 38 | t.Fatalf("error deleting bookmark: %v", err) 39 | } 40 | const expected = `` 41 | if s := buf.String(); s != expected { 42 | t.Fatalf("Wrong XML:\nwant=%s\n got=%s", expected, s) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /carbons/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature -receiver h Handler"; DO NOT EDIT. 2 | 3 | package carbons 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | 14 | // ForFeatures implements info.FeatureIter. 15 | func (h Handler) ForFeatures(node string, f func(info.Feature) error) error { 16 | if node != "" { 17 | return nil 18 | } 19 | var err error 20 | err = f(Feature) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /carbons/handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package carbons 6 | 7 | import ( 8 | "encoding/xml" 9 | 10 | "mellium.im/xmlstream" 11 | "mellium.im/xmpp/mux" 12 | "mellium.im/xmpp/stanza" 13 | ) 14 | 15 | // Handle returns an option that registers a handler for carbon copied messages 16 | // on the multiplexer. 17 | func Handle(h Handler) mux.Option { 18 | return func(m *mux.ServeMux) { 19 | recv := xml.Name{Space: NS, Local: "received"} 20 | mux.Message(stanza.NormalMessage, recv, h)(m) 21 | mux.Message(stanza.ChatMessage, recv, h)(m) 22 | 23 | sent := xml.Name{Space: NS, Local: "sent"} 24 | mux.Message(stanza.NormalMessage, sent, h)(m) 25 | mux.Message(stanza.ChatMessage, sent, h)(m) 26 | } 27 | } 28 | 29 | // Handler can be used to handle incoming carbon copied messages. 30 | type Handler struct { 31 | F func(m stanza.Message, sent bool, inner xml.TokenReader) error 32 | } 33 | 34 | // HandleMessage satisfies mux.MessageHandler. 35 | // it is used by the multiplexer and normally does not need to be called by the 36 | // user. 37 | func (h Handler) HandleMessage(p stanza.Message, r xmlstream.TokenReadEncoder) error { 38 | // Pop the message start. 39 | _, err := r.Token() 40 | if err != nil { 41 | return err 42 | } 43 | iter := xmlstream.NewIter(r) 44 | for iter.Next() { 45 | start, child := iter.Current() 46 | if start.Name.Space == NS && (start.Name.Local == "received" || start.Name.Local == "sent") { 47 | // Skip the "forwarded" element. 48 | _, err := child.Token() 49 | if err != nil { 50 | return err 51 | } 52 | return h.F(p, start.Name.Local == "sent", xmlstream.Inner(child)) 53 | } 54 | } 55 | return iter.Err() 56 | } 57 | -------------------------------------------------------------------------------- /carbons/handler_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package carbons_test 6 | 7 | import ( 8 | "context" 9 | "encoding/xml" 10 | "strings" 11 | "testing" 12 | 13 | "mellium.im/xmpp/carbons" 14 | "mellium.im/xmpp/internal/xmpptest" 15 | "mellium.im/xmpp/mux" 16 | "mellium.im/xmpp/stanza" 17 | ) 18 | 19 | func TestHandler(t *testing.T) { 20 | wait := make(chan struct{}) 21 | h := carbons.Handler{ 22 | F: func(m stanza.Message, sent bool, inner xml.TokenReader) error { 23 | defer close(wait) 24 | if sent { 25 | t.Error("expected received not to set sent") 26 | } 27 | tok, err := inner.Token() 28 | if err != nil { 29 | return err 30 | } 31 | if start, ok := tok.(xml.StartElement); !ok || start.Name.Local != "message" { 32 | t.Errorf("inner payload not handled correctly, got %T token: %[1]v", tok) 33 | } 34 | return nil 35 | }, 36 | } 37 | m := mux.New(stanza.NSClient, carbons.Handle(h)) 38 | s := xmpptest.NewClientServer(xmpptest.ClientHandler(m)) 39 | defer s.Close() 40 | const recv = `What man art thou that, thus bescreen'd in night, so stumblest on my counsel?0e3141cd80894871a68e6fe6b1ec56fa` 44 | d := xml.NewDecoder(strings.NewReader(recv)) 45 | err := s.Server.Send(context.Background(), d) 46 | if err != nil { 47 | t.Fatalf("error sending carbon: %v", err) 48 | } 49 | <-wait 50 | } 51 | -------------------------------------------------------------------------------- /color/.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | -------------------------------------------------------------------------------- /color/color_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package color_test 6 | 7 | import ( 8 | "fmt" 9 | "image/color" 10 | "strconv" 11 | "testing" 12 | 13 | xmppcolor "mellium.im/xmpp/color" 14 | ) 15 | 16 | var _ fmt.Stringer = xmppcolor.CVD(0) 17 | 18 | func TestSize(t *testing.T) { 19 | h := xmppcolor.Hash(xmppcolor.None) 20 | if size := h.Size(); size != 2 { 21 | t.Errorf("Bad size: want=%d, got=%d", 2, size) 22 | } 23 | } 24 | 25 | var colorTests = [...]struct { 26 | s string 27 | lum uint8 28 | cvd xmppcolor.CVD 29 | c color.YCbCr 30 | panic bool 31 | }{ 32 | 0: {cvd: 4, panic: true}, 33 | 1: {s: "Romeo", lum: 1, c: color.YCbCr{1, 255, 45}}, 34 | 2: {s: "juliet@capulet.lit", lum: 2, c: color.YCbCr{2, 0, 55}}, 35 | 3: {s: "😺", lum: 255, c: color.YCbCr{255, 255, 57}}, 36 | 4: {s: "council", c: color.YCbCr{0, 255, 127}}, 37 | 5: {cvd: xmppcolor.RedGreen, s: "Romeo", c: color.YCbCr{0, 0, 209}}, 38 | 6: {cvd: xmppcolor.RedGreen, s: "juliet@capulet.lit", c: color.YCbCr{0, 255, 199}}, 39 | 7: {cvd: xmppcolor.RedGreen, s: "😺", c: color.YCbCr{0, 0, 197}}, 40 | 8: {cvd: xmppcolor.RedGreen, s: "council", c: color.YCbCr{0, 0, 127}}, 41 | 9: {cvd: xmppcolor.Blue, s: "Romeo", c: color.YCbCr{0, 0, 209}}, 42 | 10: {cvd: xmppcolor.Blue, s: "juliet@capulet.lit", c: color.YCbCr{0, 0, 55}}, 43 | 11: {cvd: xmppcolor.Blue, s: "😺", c: color.YCbCr{0, 0, 197}}, 44 | 12: {cvd: xmppcolor.Blue, s: "council", c: color.YCbCr{0, 0, 127}}, 45 | } 46 | 47 | func TestHash(t *testing.T) { 48 | for i, tc := range colorTests { 49 | t.Run(strconv.Itoa(i), func(t *testing.T) { 50 | defer func() { 51 | r := recover() 52 | switch { 53 | case r == nil && tc.panic: 54 | t.Error("No panic detected, but a panic was expected") 55 | case r != nil && !tc.panic: 56 | t.Errorf("Unexpected panic detected: %v", r) 57 | } 58 | }() 59 | 60 | c := xmppcolor.String(tc.s, tc.lum, tc.cvd) 61 | if c != tc.c { 62 | t.Errorf("Invalid color value: want=%v, got=%v", tc.c, c) 63 | } 64 | }) 65 | } 66 | } 67 | 68 | func BenchmarkHash(b *testing.B) { 69 | p := []byte("a test string") 70 | h := xmppcolor.Hash(xmppcolor.None) 71 | buf := make([]byte, 0, 2) 72 | 73 | b.ResetTimer() 74 | for i := 0; i < b.N; i++ { 75 | h.Write(p) 76 | buf = h.Sum(buf) 77 | h.Reset() 78 | } 79 | } 80 | 81 | func BenchmarkBytes(b *testing.B) { 82 | p := []byte("a test string") 83 | 84 | b.ResetTimer() 85 | for i := 0; i < b.N; i++ { 86 | xmppcolor.Bytes(p, 128, xmppcolor.None) 87 | } 88 | } 89 | 90 | func BenchmarkString(b *testing.B) { 91 | for i := 0; i < b.N; i++ { 92 | xmppcolor.String("a test string", 128, xmppcolor.None) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /color/cvd_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=CVD"; DO NOT EDIT. 2 | 3 | package color 4 | 5 | import "strconv" 6 | 7 | const _CVD_name = "NoneRedGreenBlue" 8 | 9 | var _CVD_index = [...]uint8{0, 4, 12, 16} 10 | 11 | func (i CVD) String() string { 12 | if i >= CVD(len(_CVD_index)-1) { 13 | return "CVD(" + strconv.FormatInt(int64(i), 10) + ")" 14 | } 15 | return _CVD_name[_CVD_index[i]:_CVD_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /color/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package color_test 6 | 7 | import ( 8 | "image" 9 | "image/color" 10 | "image/draw" 11 | "image/png" 12 | "os" 13 | 14 | "golang.org/x/image/font" 15 | "golang.org/x/image/font/inconsolata" 16 | "golang.org/x/image/math/fixed" 17 | 18 | colorgen "mellium.im/xmpp/color" 19 | ) 20 | 21 | const ( 22 | lum = 128 23 | cvd = colorgen.None 24 | factor = 0.4 25 | inv = 1 - factor 26 | ) 27 | 28 | // naively mix a foreground color with a background color ignoring the alpha 29 | // channel. 30 | func mix(fg color.Color, bg color.Color) color.Color { 31 | rb, gb, bb, _ := bg.RGBA() 32 | rf, gf, bf, _ := fg.RGBA() 33 | 34 | const maxu16 = 1<<16 - 1 35 | return color.RGBA{ 36 | R: uint8(factor*float32(maxu16-rb) + inv*float32(rf)), 37 | G: uint8(factor*float32(maxu16-gb) + inv*float32(gf)), 38 | B: uint8(factor*float32(maxu16-bb) + inv*float32(bf)), 39 | A: 255, 40 | } 41 | } 42 | 43 | func Example() { 44 | strs := []string{ 45 | "Beautiful", 46 | "Catchup", 47 | "Dandelion", 48 | "Fuego Borrego", 49 | "Green Giant", 50 | "Mailman", 51 | "Papa Shrimp", 52 | "Pockets", 53 | "Spoon Foot", 54 | "Sunshine", 55 | "Thespian", 56 | "Twinkle Toes", 57 | "Zodiac", 58 | } 59 | 60 | img := image.NewRGBA(image.Rect(0, 0, 300, 216)) 61 | parts := []color.Color{color.Black, color.White} 62 | 63 | for x, bg := range parts { 64 | bounds := img.Bounds() 65 | w := bounds.Max.X / len(parts) 66 | bounds.Min.X = w * x 67 | bounds.Max.X = w * (x + 1) 68 | draw.Draw(img, bounds, &image.Uniform{bg}, image.Point{}, draw.Src) 69 | 70 | for y, s := range strs { 71 | d := &font.Drawer{ 72 | Dst: img, 73 | Src: image.NewUniform(mix( 74 | colorgen.String(s, lum, cvd), 75 | bg, 76 | )), 77 | Face: inconsolata.Regular8x16, 78 | Dot: fixed.Point26_6{ 79 | X: fixed.Int26_6((12 + bounds.Min.X) * 64), 80 | Y: fixed.Int26_6(16 * (y + 1) * 64), 81 | }, 82 | } 83 | 84 | d.DrawString(s) 85 | } 86 | } 87 | 88 | f, err := os.Create("gonicks.png") 89 | if err != nil { 90 | panic(err) 91 | } 92 | defer f.Close() 93 | if err := png.Encode(f, img); err != nil { 94 | panic(err) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /commands/actions_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Actions -linecomment"; DO NOT EDIT. 2 | 3 | package commands 4 | 5 | import "strconv" 6 | 7 | const ( 8 | _Actions_name_0 = "prevnext" 9 | _Actions_name_1 = "complete" 10 | ) 11 | 12 | var ( 13 | _Actions_index_0 = [...]uint8{0, 4, 8} 14 | ) 15 | 16 | func (i Actions) String() string { 17 | switch { 18 | case 1 <= i && i <= 2: 19 | i -= 1 20 | return _Actions_name_0[_Actions_index_0[i]:_Actions_index_0[i+1]] 21 | case i == 4: 22 | return _Actions_name_1 23 | default: 24 | return "Actions(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /commands/actions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package commands_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "mellium.im/xmpp/commands" 11 | "mellium.im/xmpp/internal/xmpptest" 12 | ) 13 | 14 | func TestActions(t *testing.T) { 15 | xmpptest.RunEncodingTests(t, []xmpptest.EncodingTestCase{ 16 | { 17 | Value: func() *commands.Actions { 18 | action := commands.Prev | commands.Next | commands.Complete 19 | return &action 20 | }(), 21 | XML: ``, 22 | }, 23 | { 24 | Value: func() *commands.Actions { 25 | action := commands.Next | commands.Complete | (commands.Next << 3) 26 | return &action 27 | }(), 28 | XML: ``, 29 | }, 30 | { 31 | Value: func() *commands.Actions { 32 | action := commands.Next | commands.Complete | (commands.Prev << 3) 33 | return &action 34 | }(), 35 | XML: ``, 36 | }, 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /commands/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature"; DO NOT EDIT. 2 | 3 | package commands 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | -------------------------------------------------------------------------------- /commands/iter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package commands 6 | 7 | import ( 8 | "context" 9 | 10 | "mellium.im/xmpp" 11 | "mellium.im/xmpp/disco" 12 | "mellium.im/xmpp/jid" 13 | "mellium.im/xmpp/stanza" 14 | ) 15 | 16 | // Iter is an iterator over Command's. 17 | type Iter struct { 18 | *disco.ItemIter 19 | } 20 | 21 | // Command returns the last command parsed by the iterator. 22 | func (i Iter) Command() Command { 23 | item := i.Item() 24 | return Command{ 25 | JID: item.JID, 26 | Name: item.Name, 27 | Node: item.Node, 28 | } 29 | } 30 | 31 | // Fetch requests a list of commands. 32 | // 33 | // The iterator must be closed before anything else is done on the session or it 34 | // will become invalid. 35 | // Any errors encountered while creating the iter are deferred until the iter is 36 | // used. 37 | func Fetch(ctx context.Context, to jid.JID, s *xmpp.Session) Iter { 38 | return FetchIQ(ctx, stanza.IQ{To: to}, s) 39 | } 40 | 41 | // FetchIQ is like Fetch but it allows you to customize the IQ. 42 | // Changing the type of the provided IQ has no effect. 43 | func FetchIQ(ctx context.Context, iq stanza.IQ, s *xmpp.Session) Iter { 44 | return Iter{ItemIter: disco.FetchItemsIQ(ctx, NS, iq, s)} 45 | } 46 | -------------------------------------------------------------------------------- /commands/notes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package commands 6 | 7 | //go:generate go run -tags=tools golang.org/x/tools/cmd/stringer -type=NoteType -linecomment 8 | 9 | import ( 10 | "encoding/xml" 11 | "fmt" 12 | 13 | "mellium.im/xmlstream" 14 | ) 15 | 16 | // NoteType indicates the severity of a note. 17 | // It should always be one of the pre-defined constants. 18 | type NoteType int8 19 | 20 | // MarshalXMLAttr satisfies xml.MarshalerAttr. 21 | func (n NoteType) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { 22 | var err error 23 | if n < NoteInfo || n > NoteError { 24 | err = fmt.Errorf("invalid note type %s", n) 25 | } 26 | return xml.Attr{Name: name, Value: n.String()}, err 27 | } 28 | 29 | // UnmarshalXMLAttr satisfies xml.UnmarshalerAttr. 30 | func (n *NoteType) UnmarshalXMLAttr(attr xml.Attr) error { 31 | switch attr.Value { 32 | case "info": 33 | *n = NoteInfo 34 | case "warn": 35 | *n = NoteWarn 36 | case "error": 37 | *n = NoteError 38 | default: 39 | *n = -1 40 | return fmt.Errorf("invalid note attribute %s", attr.Value) 41 | } 42 | return nil 43 | } 44 | 45 | // A list of possible NoteType's. 46 | const ( 47 | NoteInfo NoteType = iota // info 48 | NoteWarn // warn 49 | NoteError // error 50 | ) 51 | 52 | // Note provides information about the status of a command and may be returned 53 | // as part of the response payload. 54 | type Note struct { 55 | XMLName xml.Name `xml:"note"` 56 | Type NoteType `xml:"type,attr"` 57 | Value string `xml:",cdata"` 58 | } 59 | 60 | // TokenReader satisfies the xmlstream.Marshaler interface. 61 | func (n Note) TokenReader() xml.TokenReader { 62 | /* #nosec */ 63 | attr, _ := n.Type.MarshalXMLAttr(xml.Name{Local: "type"}) 64 | return xmlstream.Wrap(xmlstream.Token(xml.CharData(n.Value)), xml.StartElement{ 65 | Name: xml.Name{Local: "note"}, 66 | Attr: []xml.Attr{attr}, 67 | }) 68 | } 69 | 70 | // WriteXML satisfies the xmlstream.WriterTo interface. 71 | func (n Note) WriteXML(w xmlstream.TokenWriter) (int, error) { 72 | return xmlstream.Copy(w, n.TokenReader()) 73 | } 74 | 75 | // MarshalXML implements xml.Marshaler. 76 | func (n Note) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { 77 | _, err := n.WriteXML(e) 78 | return err 79 | } 80 | -------------------------------------------------------------------------------- /commands/notes_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package commands_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "testing" 10 | 11 | "mellium.im/xmpp/commands" 12 | "mellium.im/xmpp/internal/xmpptest" 13 | ) 14 | 15 | func TestNotes(t *testing.T) { 16 | xmpptest.RunEncodingTests(t, []xmpptest.EncodingTestCase{ 17 | { 18 | Value: &commands.Note{XMLName: xml.Name{Local: "note"}}, 19 | XML: ``, 20 | }, 21 | { 22 | Value: &commands.Note{XMLName: xml.Name{Local: "note"}, Type: commands.NoteError, Value: "foo"}, 23 | XML: `foo`, 24 | }, 25 | { 26 | Value: &commands.Note{XMLName: xml.Name{Local: "note"}, Type: commands.NoteType(5), Value: "foo"}, 27 | XML: `foo`, 28 | NoUnmarshal: true, 29 | }, 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /commands/notetype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=NoteType -linecomment"; DO NOT EDIT. 2 | 3 | package commands 4 | 5 | import "strconv" 6 | 7 | const _NoteType_name = "infowarnerror" 8 | 9 | var _NoteType_index = [...]uint8{0, 4, 8, 13} 10 | 11 | func (i NoteType) String() string { 12 | if i < 0 || i >= NoteType(len(_NoteType_index)-1) { 13 | return "NoteType(" + strconv.FormatInt(int64(i), 10) + ")" 14 | } 15 | return _NoteType_name[_NoteType_index[i]:_NoteType_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /commands/response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package commands 6 | 7 | import ( 8 | "encoding/xml" 9 | 10 | "mellium.im/xmlstream" 11 | "mellium.im/xmpp/stanza" 12 | ) 13 | 14 | // Response is the response to a command. 15 | // It may contain other commands that can be execute in sequence. 16 | type Response struct { 17 | stanza.IQ 18 | 19 | Node string `xml:"node,attr"` 20 | SID string `xml:"sessionid,attr"` 21 | Status string `xml:"status,attr"` 22 | } 23 | 24 | // Cancel ends the multi-stage command. 25 | func (r Response) Cancel() Command { 26 | return Command{ 27 | JID: r.IQ.From, 28 | SID: r.SID, 29 | Node: r.Node, 30 | Action: "cancel", 31 | } 32 | } 33 | 34 | // Complete ends the multi-stage command and optionally submits data. 35 | func (r Response) Complete() Command { 36 | return Command{ 37 | JID: r.IQ.From, 38 | SID: r.SID, 39 | Node: r.Node, 40 | Action: "complete", 41 | } 42 | } 43 | 44 | // Next requests the next step in a multi-stage command. 45 | func (r Response) Next() Command { 46 | return Command{ 47 | JID: r.IQ.From, 48 | SID: r.SID, 49 | Node: r.Node, 50 | Action: "next", 51 | } 52 | } 53 | 54 | // Prev requests the previous step in a multi-stage command. 55 | func (r Response) Prev() Command { 56 | return Command{ 57 | JID: r.IQ.From, 58 | SID: r.SID, 59 | Node: r.Node, 60 | Action: "prev", 61 | } 62 | } 63 | 64 | // TokenReader satisfies the xmlstream.Marshaler interface. 65 | func (r Response) TokenReader() xml.TokenReader { 66 | return r.IQ.Wrap(xmlstream.Wrap( 67 | nil, 68 | xml.StartElement{ 69 | Name: xml.Name{Space: NS, Local: "command"}, 70 | Attr: []xml.Attr{ 71 | {Name: xml.Name{Local: "node"}, Value: r.Node}, 72 | {Name: xml.Name{Local: "sessionid"}, Value: r.SID}, 73 | {Name: xml.Name{Local: "status"}, Value: r.Status}, 74 | }, 75 | }, 76 | )) 77 | } 78 | 79 | // WriteXML satisfies the xmlstream.WriterTo interface. 80 | // It is like MarshalXML except it writes tokens to w. 81 | func (r Response) WriteXML(w xmlstream.TokenWriter) (n int, err error) { 82 | return xmlstream.Copy(w, r.TokenReader()) 83 | } 84 | 85 | // MarshalXML implements xml.Marshaler. 86 | func (r Response) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { 87 | _, err := r.WriteXML(e) 88 | return err 89 | } 90 | -------------------------------------------------------------------------------- /commands/response_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package commands_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "testing" 10 | 11 | "mellium.im/xmpp/commands" 12 | "mellium.im/xmpp/internal/xmpptest" 13 | ) 14 | 15 | func TestNoteTypes(t *testing.T) { 16 | xmpptest.RunEncodingTests(t, []xmpptest.EncodingTestCase{ 17 | { 18 | Value: &struct { 19 | XMLName xml.Name `xml:"foo"` 20 | Type commands.NoteType `xml:"notetype,attr"` 21 | }{ 22 | XMLName: xml.Name{Local: "foo"}, 23 | }, 24 | XML: ``, 25 | }, 26 | { 27 | Value: &struct { 28 | XMLName xml.Name `xml:"foo"` 29 | Type commands.NoteType `xml:"notetype,attr"` 30 | }{ 31 | XMLName: xml.Name{Local: "foo"}, 32 | Type: commands.NoteWarn, 33 | }, 34 | XML: ``, 35 | }, 36 | { 37 | Value: &struct { 38 | XMLName xml.Name `xml:"foo"` 39 | Type commands.NoteType `xml:"notetype,attr"` 40 | }{ 41 | XMLName: xml.Name{Local: "foo"}, 42 | Type: commands.NoteError, 43 | }, 44 | XML: ``, 45 | }, 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /crypto/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature"; DO NOT EDIT. 2 | 3 | package crypto 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | -------------------------------------------------------------------------------- /crypto/handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package crypto 6 | 7 | import ( 8 | "fmt" 9 | 10 | "mellium.im/xmpp/disco/info" 11 | ) 12 | 13 | // Features returns an iter that can be registered against a mux to advertise 14 | // support for the hash list. 15 | // The iter will return an error for any hashes that are not available in the 16 | // binary. 17 | func Features(h ...Hash) info.FeatureIter { 18 | return handler(h) 19 | } 20 | 21 | type handler []Hash 22 | 23 | func (h handler) ForFeatures(node string, f func(info.Feature) error) error { 24 | if node != "" { 25 | return nil 26 | } 27 | for _, h := range h { 28 | if !h.Available() { 29 | return fmt.Errorf("%w %s", ErrUnlinkedAlgo, h.String()) 30 | } 31 | ns, err := h.Namespace() 32 | if err != nil { 33 | return err 34 | } 35 | err = f(info.Feature{Var: ns}) 36 | if err != nil { 37 | return err 38 | } 39 | } 40 | return f(Feature) 41 | } 42 | -------------------------------------------------------------------------------- /crypto/handler_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package crypto_test 6 | 7 | import ( 8 | _ "crypto/sha256" 9 | "errors" 10 | "reflect" 11 | "strconv" 12 | "testing" 13 | 14 | "mellium.im/xmpp/crypto" 15 | "mellium.im/xmpp/disco/info" 16 | ) 17 | 18 | var iterTests = []struct { 19 | node string 20 | h []crypto.Hash 21 | vars []string 22 | err error 23 | }{ 24 | 0: { 25 | h: []crypto.Hash{crypto.SHA256, crypto.BLAKE2b_512}, 26 | vars: []string{"urn:xmpp:hash-function-text-names:sha-256"}, 27 | err: crypto.ErrUnlinkedAlgo, 28 | }, 29 | 1: { 30 | node: "test", 31 | h: []crypto.Hash{crypto.SHA256}, 32 | }, 33 | } 34 | 35 | func TestIter(t *testing.T) { 36 | for i, tc := range iterTests { 37 | t.Run(strconv.Itoa(i), func(t *testing.T) { 38 | iter := crypto.Features(tc.h...) 39 | var found []string 40 | err := iter.ForFeatures(tc.node, func(feature info.Feature) error { 41 | found = append(found, feature.Var) 42 | return nil 43 | }) 44 | if len(found) != len(tc.vars) { 45 | t.Fatalf("wrong length for iter: want=%d, got=%d", len(tc.vars), len(found)) 46 | } 47 | if !reflect.DeepEqual(tc.vars, found) { 48 | t.Errorf("found incorrect hashes: want=%v, got=%v", tc.h, found) 49 | } 50 | if tc.err != nil || err != nil { 51 | if !errors.Is(err, tc.err) { 52 | t.Errorf("expected unlinked algo error, found: %v", err) 53 | } 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /crypto/trustmsgexport_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package crypto 6 | 7 | // ErrTrustElement is exported only during testing for use by the _test package. 8 | var ErrTrustElement = errTrustElement 9 | -------------------------------------------------------------------------------- /design/README.md: -------------------------------------------------------------------------------- 1 | # Proposals and Design Documents 2 | 3 | When working on a large change, you may be asked to first submit a design 4 | document to make the change easier to understand and evaluate. 5 | A template for proposals and design documents may be found in the file 6 | [template.md]. 7 | 8 | The process involves: 9 | 10 | 1. Creating an issue at https://mellium.im/issue 11 | 2. A discussion on the issue tracker that may result in a request for a design 12 | document 13 | 3. The proposal author writes a design doc to work out any details and open 14 | issues and make the proposal more concrete 15 | 4. The design doc may go through several revisions and further discussion 16 | 5. The proposal is accepted or declined 17 | 18 | Finally, if accepted, the proposal may be implemented and the patch set merged! 19 | 20 | If you need help with this process, feel free to join the 21 | [chat room](https://mellium.chat). 22 | 23 | [template.md]: https://mellium.im/design/template 24 | -------------------------------------------------------------------------------- /design/template.md: -------------------------------------------------------------------------------- 1 | # Title 2 | 3 | **Author(s):** Your name 4 | **Last updated:** 2020-04-06 5 | **Discussion:** https://mellium.im/issue/{issue number} 6 | 7 | ## Abstract 8 | 9 | A sentence or two explaining *what* is being proposed. 10 | 11 | 12 | ## Terminology 13 | 14 | This section includes any terminology that needs to be defined to understand the 15 | rest of the document. This SHOULD include the latest text from [BCP 14]: 16 | 17 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL 18 | NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", 19 | "MAY", and "OPTIONAL" in this document are to be interpreted as 20 | described in BCP 14 [RFC2119] [RFC8174] when, and only when, they 21 | appear in all capitals, as shown here. 22 | 23 | [BCP 14]: https://tools.ietf.org/html/bcp14 24 | 25 | 26 | ## Background 27 | 28 | A longer description explaining what is being proposed in more detail, but also 29 | why it is needed and the history of the issue or the feature in Mellium if 30 | applicable, as well as any necessary background information required to fully 31 | understand and evaluate the proposal. 32 | 33 | 34 | ## Requirements 35 | 36 | - This will likely be a bulleted list 37 | - Each requirement should be met by the proposal in the next section 38 | 39 | 40 | ## Proposal 41 | 42 | The actual proposal. 43 | This should include the entire public API and a summary of what this will mean 44 | for compatibility and maintenance overhead going forward. 45 | For example, it might include the various types, functions and methods in a 46 | fenced code block. 47 | This is a good place to go ahead and think about your documentation and how you 48 | will explain to users what your types, methods, and functions do and how they 49 | should use them. 50 | For example: 51 | 52 | ``` 53 | // Cmd is an external command being prepared or run. 54 | type Cmd struct { 55 | *exec.Cmd 56 | … 57 | } 58 | 59 | // New creates a new, unstarted, command. 60 | func New(ctx context.Context, name string, opts ...Option) (*Cmd, error) 61 | 62 | // Close kills the command if it is still running and cleans up any 63 | // temporary resources that were created. 64 | func (cmd *Cmd) Close() error 65 | 66 | // ConfigDir returns the temporary directory used to store config files. 67 | func (cmd *Cmd) ConfigDir() string 68 | 69 | // Dial attempts to connect to the server by dialing localhost and then 70 | // negotiating a stream with the location set to the domainpart of j and the 71 | // origin set to j. 72 | func (cmd *Cmd) Dial(ctx context.Context, j jid.JID, t *testing.T, features ...xmpp.StreamFeature) (*xmpp.Session, error) 73 | ``` 74 | 75 | ## Open Issues 76 | 77 | A list of known issues that still need to be addressed. 78 | This section may be omitted if there are no open issues. 79 | -------------------------------------------------------------------------------- /dial/integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build integration 6 | // +build integration 7 | 8 | package dial_test 9 | 10 | import ( 11 | "context" 12 | "net" 13 | "reflect" 14 | "strings" 15 | "testing" 16 | "time" 17 | 18 | "mellium.im/xmpp/dial" 19 | "mellium.im/xmpp/jid" 20 | ) 21 | 22 | var dialTests = []struct { 23 | dialer dial.Dialer 24 | domain string 25 | err string 26 | errType error 27 | }{ 28 | { 29 | dialer: dial.Dialer{S2S: true}, 30 | domain: "no-target.badxmpp.eu", 31 | errType: &net.OpError{}, 32 | }, 33 | { 34 | dialer: dial.Dialer{S2S: true}, 35 | domain: "no-address.badxmpp.eu", 36 | errType: &net.OpError{}, 37 | }, 38 | { 39 | dialer: dial.Dialer{S2S: true, NoTLS: true}, 40 | domain: "no-service.badxmpp.eu", 41 | err: "no xmpp service found at address no-service.badxmpp.eu", 42 | }, 43 | { 44 | // If one service returns "." (no service at this address) try the other 45 | // (which in this case is still an error). 46 | dialer: dial.Dialer{S2S: true}, 47 | domain: "no-service.badxmpp.eu", 48 | errType: &net.OpError{}, 49 | }, 50 | } 51 | 52 | func TestIntegrationDial(t *testing.T) { 53 | for _, tc := range dialTests { 54 | t.Run(tc.domain, func(t *testing.T) { 55 | tries := 3 56 | retry: 57 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 58 | defer cancel() 59 | j := jid.MustParse("test@" + tc.domain) 60 | _, err := tc.dialer.Dial(ctx, "tcp", j) 61 | if dnsErr, ok := err.(*net.DNSError); tries > 0 && ok && (dnsErr.Temporary() || dnsErr.Timeout()) { 62 | tries-- 63 | t.Logf("DNS lookup failed for %s, retries remaining %d: %v", tc.domain, tries, err) 64 | goto retry 65 | } 66 | switch { 67 | case tc.err != "" && err == nil: 68 | t.Errorf("expected error if SRV record target is missing, got none") 69 | case tc.err != "" && !strings.HasSuffix(err.Error(), tc.err): 70 | t.Errorf("wrong error: want=%s, got=%v", tc.err, err.Error()) 71 | case tc.err == "" && tc.errType != nil: 72 | if reflect.TypeOf(tc.errType) != reflect.TypeOf(err) { 73 | t.Errorf("wrong type of error: want=%T(%#v), got=%T(%#v)", tc.errType, tc.errType, err, err) 74 | } 75 | case tc.err == "" && err != nil: 76 | t.Errorf("got unexpected error: %v", err) 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /disco/disco.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:generate go run gen.go 6 | //go:generate go run ../internal/genfeature -filename features.go -receiver "h *discoHandler" -vars Feature:NSInfo 7 | 8 | // Package disco implements service discovery. 9 | package disco // import "mellium.im/xmpp/disco" 10 | 11 | // Namespaces used by this package. 12 | const ( 13 | NSInfo = `http://jabber.org/protocol/disco#info` 14 | NSItems = `http://jabber.org/protocol/disco#items` 15 | NSCaps = `http://jabber.org/protocol/caps` 16 | ) 17 | -------------------------------------------------------------------------------- /disco/features.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature -filename features.go -receiver h *discoHandler -vars Feature:NSInfo"; DO NOT EDIT. 2 | 3 | package disco 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NSInfo} 12 | ) 13 | 14 | // ForFeatures implements info.FeatureIter. 15 | func (h *discoHandler) ForFeatures(node string, f func(info.Feature) error) error { 16 | if node != "" { 17 | return nil 18 | } 19 | var err error 20 | err = f(Feature) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /disco/info/feature.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package info contains service discovery features. 6 | // 7 | // These were separated out into a separate package to prevent import loops. 8 | package info // import "mellium.im/xmpp/disco/info" 9 | 10 | import ( 11 | "encoding/xml" 12 | 13 | "mellium.im/xmlstream" 14 | ) 15 | 16 | const ( 17 | nsInfo = `http://jabber.org/protocol/disco#info` 18 | ) 19 | 20 | // Feature represents a feature supported by an entity on the network. 21 | type Feature struct { 22 | XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info feature"` 23 | Var string `xml:"var,attr"` 24 | } 25 | 26 | // TokenReader implements xmlstream.Marshaler. 27 | func (f Feature) TokenReader() xml.TokenReader { 28 | return xmlstream.Wrap(nil, xml.StartElement{ 29 | Name: xml.Name{Space: nsInfo, Local: "feature"}, 30 | Attr: []xml.Attr{{ 31 | Name: xml.Name{Local: "var"}, 32 | Value: f.Var, 33 | }}, 34 | }) 35 | } 36 | 37 | // WriteXML implements xmlstream.WriterTo. 38 | func (f Feature) WriteXML(w xmlstream.TokenWriter) (int, error) { 39 | return xmlstream.Copy(w, f.TokenReader()) 40 | } 41 | 42 | // MarshalXML implements xml.Marshaler. 43 | func (f Feature) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { 44 | _, err := f.WriteXML(e) 45 | return err 46 | } 47 | 48 | // FeatureIter is the interface implemented by types that implement disco 49 | // features. 50 | type FeatureIter interface { 51 | ForFeatures(node string, f func(Feature) error) error 52 | } 53 | -------------------------------------------------------------------------------- /disco/info/feature_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package info_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "testing" 10 | 11 | "mellium.im/xmlstream" 12 | "mellium.im/xmpp/disco" 13 | "mellium.im/xmpp/disco/info" 14 | "mellium.im/xmpp/internal/xmpptest" 15 | ) 16 | 17 | var ( 18 | _ xml.Marshaler = info.Feature{} 19 | _ xmlstream.Marshaler = info.Feature{} 20 | _ xmlstream.WriterTo = info.Feature{} 21 | ) 22 | 23 | func TestEncodeFeature(t *testing.T) { 24 | xmpptest.RunEncodingTests(t, []xmpptest.EncodingTestCase{ 25 | 0: { 26 | Value: &info.Feature{}, 27 | XML: ``, 28 | NoUnmarshal: true, 29 | }, 30 | 1: { 31 | Value: &info.Feature{ 32 | XMLName: xml.Name{Space: disco.NSInfo, Local: "feature"}, 33 | Var: "urn:example", 34 | }, 35 | XML: ``, 36 | }, 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /disco/info/identity.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package info 6 | 7 | import ( 8 | "encoding/xml" 9 | 10 | "mellium.im/xmlstream" 11 | "mellium.im/xmpp/internal/ns" 12 | ) 13 | 14 | // Identity is the type and category of a node on the network. 15 | // Normally one of the pre-defined Identity types should be used. 16 | type Identity struct { 17 | XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info identity"` 18 | Category string `xml:"category,attr"` 19 | Type string `xml:"type,attr"` 20 | Name string `xml:"name,attr,omitempty"` 21 | Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"` 22 | } 23 | 24 | // TokenReader implements xmlstream.Marshaler. 25 | func (i Identity) TokenReader() xml.TokenReader { 26 | start := xml.StartElement{ 27 | Name: xml.Name{Space: nsInfo, Local: "identity"}, 28 | Attr: []xml.Attr{{ 29 | Name: xml.Name{Local: "category"}, 30 | Value: i.Category, 31 | }, { 32 | Name: xml.Name{Local: "type"}, 33 | Value: i.Type, 34 | }}, 35 | } 36 | if i.Name != "" { 37 | start.Attr = append(start.Attr, xml.Attr{ 38 | Name: xml.Name{Local: "name"}, Value: i.Name, 39 | }) 40 | } 41 | if i.Lang != "" { 42 | start.Attr = append(start.Attr, xml.Attr{ 43 | Name: xml.Name{Space: ns.XML, Local: "lang"}, Value: i.Lang, 44 | }) 45 | } 46 | return xmlstream.Wrap(nil, start) 47 | } 48 | 49 | // WriteXML implements xmlstream.WriterTo. 50 | func (i Identity) WriteXML(w xmlstream.TokenWriter) (int, error) { 51 | return xmlstream.Copy(w, i.TokenReader()) 52 | } 53 | 54 | // MarshalXML implements xml.Marshaler. 55 | func (i Identity) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { 56 | _, err := i.WriteXML(e) 57 | return err 58 | } 59 | 60 | // IdentityIter is the interface implemented by types that implement disco 61 | // identities. 62 | type IdentityIter interface { 63 | ForIdentities(node string, f func(Identity) error) error 64 | } 65 | -------------------------------------------------------------------------------- /disco/info/identity_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package info_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "testing" 10 | 11 | "mellium.im/xmlstream" 12 | "mellium.im/xmpp/disco" 13 | "mellium.im/xmpp/disco/info" 14 | "mellium.im/xmpp/internal/xmpptest" 15 | ) 16 | 17 | var ( 18 | _ xml.Marshaler = info.Identity{} 19 | _ xmlstream.Marshaler = info.Identity{} 20 | _ xmlstream.WriterTo = info.Identity{} 21 | ) 22 | 23 | func TestEncodeIdentity(t *testing.T) { 24 | xmpptest.RunEncodingTests(t, []xmpptest.EncodingTestCase{ 25 | 0: { 26 | Value: &info.Identity{}, 27 | XML: ``, 28 | NoUnmarshal: true, 29 | }, 30 | 1: { 31 | Value: &info.Identity{ 32 | XMLName: xml.Name{Space: disco.NSInfo, Local: "identity"}, 33 | Category: "cat", 34 | Type: "typ", 35 | Name: "name", 36 | }, 37 | XML: ``, 38 | }, 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /disco/integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build integration 6 | // +build integration 7 | 8 | package disco_test 9 | 10 | import ( 11 | "context" 12 | "crypto/tls" 13 | "testing" 14 | 15 | "mellium.im/sasl" 16 | "mellium.im/xmpp" 17 | "mellium.im/xmpp/disco" 18 | "mellium.im/xmpp/internal/integration" 19 | "mellium.im/xmpp/internal/integration/ejabberd" 20 | "mellium.im/xmpp/internal/integration/jackal" 21 | "mellium.im/xmpp/internal/integration/prosody" 22 | ) 23 | 24 | func TestIntegrationInfo(t *testing.T) { 25 | prosodyRun := prosody.Test(context.TODO(), t, 26 | integration.Log(), 27 | prosody.ListenC2S(), 28 | ) 29 | prosodyRun(integrationRequestInfo) 30 | 31 | ejabberdRun := ejabberd.Test(context.TODO(), t, 32 | integration.Log(), 33 | ejabberd.ListenC2S(), 34 | ) 35 | ejabberdRun(integrationRequestInfo) 36 | 37 | jackalRun := jackal.Test(context.TODO(), t, 38 | integration.Log(), 39 | jackal.ListenC2S(), 40 | ) 41 | jackalRun(integrationRequestInfo) 42 | } 43 | 44 | func integrationRequestInfo(ctx context.Context, t *testing.T, cmd *integration.Cmd) { 45 | j, pass := cmd.User() 46 | session, err := cmd.DialClient(ctx, j, t, 47 | xmpp.StartTLS(&tls.Config{ 48 | InsecureSkipVerify: true, 49 | }), 50 | xmpp.SASL("", pass, sasl.Plain, sasl.ScramSha256), 51 | xmpp.BindResource(), 52 | ) 53 | if err != nil { 54 | t.Fatalf("error connecting: %v", err) 55 | } 56 | go func() { 57 | err := session.Serve(nil) 58 | if err != nil { 59 | t.Logf("error from serve: %v", err) 60 | } 61 | }() 62 | 63 | info, err := disco.GetInfo(ctx, "", j.Domain(), session) 64 | if err != nil { 65 | t.Errorf("error getting info: %v", err) 66 | } 67 | if len(info.Features) == 0 { 68 | t.Errorf("expected to get features back") 69 | } 70 | if len(info.Identity) == 0 { 71 | t.Errorf("expected to get identities back") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /disco/items/items.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package items contains service discovery items. 6 | // 7 | // These were separated out into a separate package to prevent import loops. 8 | package items // import "mellium.im/xmpp/disco/items" 9 | 10 | import ( 11 | "encoding/xml" 12 | 13 | "mellium.im/xmlstream" 14 | "mellium.im/xmpp/jid" 15 | ) 16 | 17 | const ( 18 | ns = `http://jabber.org/protocol/disco#items` 19 | ) 20 | 21 | // Item represents a discovered item. 22 | type Item struct { 23 | XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items item"` 24 | JID jid.JID `xml:"jid,attr"` 25 | Name string `xml:"name,attr,omitempty"` 26 | Node string `xml:"node,attr,omitempty"` 27 | } 28 | 29 | // TokenReader implements xmlstream.Marshaler. 30 | func (i Item) TokenReader() xml.TokenReader { 31 | start := xml.StartElement{ 32 | Name: xml.Name{Space: ns, Local: "item"}, 33 | Attr: []xml.Attr{{ 34 | Name: xml.Name{Local: "jid"}, 35 | Value: i.JID.String(), 36 | }}, 37 | } 38 | if i.Node != "" { 39 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "node"}, Value: i.Node}) 40 | } 41 | if i.Name != "" { 42 | start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "name"}, Value: i.Name}) 43 | } 44 | return xmlstream.Wrap(nil, start) 45 | } 46 | 47 | // WriteXML implements xmlstream.WriterTo. 48 | func (i Item) WriteXML(w xmlstream.TokenWriter) (int, error) { 49 | return xmlstream.Copy(w, i.TokenReader()) 50 | } 51 | 52 | // MarshalXML implements xml.Marshaler. 53 | func (i Item) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { 54 | _, err := i.WriteXML(e) 55 | return err 56 | } 57 | 58 | // Iter is the interface implemented by types that respond to service discovery 59 | // requests for items. 60 | type Iter interface { 61 | ForItems(node string, f func(Item) error) error 62 | } 63 | -------------------------------------------------------------------------------- /disco/items/items_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package items_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "testing" 10 | 11 | "mellium.im/xmlstream" 12 | "mellium.im/xmpp/disco" 13 | "mellium.im/xmpp/disco/items" 14 | "mellium.im/xmpp/internal/xmpptest" 15 | "mellium.im/xmpp/jid" 16 | ) 17 | 18 | var ( 19 | _ xml.Marshaler = items.Item{} 20 | _ xmlstream.Marshaler = items.Item{} 21 | _ xmlstream.WriterTo = items.Item{} 22 | ) 23 | 24 | func TestEncode(t *testing.T) { 25 | xmpptest.RunEncodingTests(t, []xmpptest.EncodingTestCase{ 26 | 0: { 27 | Value: &items.Item{}, 28 | XML: ``, 29 | NoUnmarshal: true, 30 | }, 31 | 1: { 32 | Value: &items.Item{ 33 | XMLName: xml.Name{Space: disco.NSItems, Local: "item"}, 34 | JID: jid.MustParse("example.net"), 35 | Node: "urn:example", 36 | Name: "test", 37 | }, 38 | XML: ``, 39 | }, 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /docs/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: mellium 4 | patreon: # samwhited 5 | open_collective: mellium 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # SamWhited 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # ['https://www.buymeacoffee.com/samwhited'] 13 | -------------------------------------------------------------------------------- /docs/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 'pkgname: ' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 19 | 20 | Add your description here. 21 | 22 | 23 | 24 | ``` 25 | A minimal working example in the body of the issue and a link to play.golang.org 26 | where it can be run. 27 | ``` 28 | 29 | https://play.golang.org/… 30 | 31 | 35 | 36 | ## Go Version and Env 37 | 38 | ``` 39 | go version … 40 | ``` 41 | 42 | 47 | 48 |
go env 49 |

50 | 51 | ``` 52 | GO111MODULE="on" 53 | GOARCH="amd64" 54 | GOBIN="" 55 | 56 | … 57 | ``` 58 | 59 |

60 |
61 | -------------------------------------------------------------------------------- /docs/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'pkgname: ' 5 | labels: 'proposal' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 28 | 29 | Add your description here. 30 | 31 | 44 | 45 | **Design doc:** TODO 46 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest major version is supported. 6 | Older versions will not receive security fixes. 7 | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | Security sensitive issues should be reported directly to the project maintainer 12 | by emailing [`security@mellium.im`]. 13 | A maintainer will respond to your report within 48 hours. 14 | 15 | ## Verifying Releases 16 | 17 | All releases will be tagged and signed with one of the following GPG signing 18 | keys: 19 | 20 | ``` 21 | 82214D7FB54DC9A3BC0CDAE116D5138E52B849B3 22 | ``` 23 | 24 | Keys may be pulled from your keyserver of choice and verifications can be 25 | performed using Git: 26 | 27 | ``` 28 | $ gpg --recv-keys 82214D7FB54DC9A3BC0CDAE116D5138E52B849B3 29 | $ git verify-tag v0.21.4 30 | ``` 31 | 32 | [`security@mellium.im`]: mailto:security@mellium.im 33 | -------------------------------------------------------------------------------- /docs/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | Except for security issues (see [SECURITY.md]), no guarantee of support is 4 | provided for this project. 5 | To negotiate commercial support, email [`support@mellium.im`]. 6 | 7 | For best-effort community support, you can try the following chat rooms: 8 | 9 | - Mellium chat: [![Mellium Chat](https://img.shields.io/badge/XMPP-users@mellium.chat-orange.svg)](https://mellium.chat) 10 | - Go chat: [![Go Chat](https://img.shields.io/badge/XMPP-go@gopher.chat-orange.svg)](https://gopher.chat) 11 | 12 | 13 | [SECURITY.md]: https://mellium.im/docs/SECURITY 14 | [`support@mellium.im`]: mailto:support@mellium.im 15 | -------------------------------------------------------------------------------- /docs/dial.gv: -------------------------------------------------------------------------------- 1 | digraph { 2 | label="Discovery and Connection" 3 | 4 | dial[shape="oval", label="dial.Client()"]; 5 | 6 | xmppssrv[shape="box", style=rounded, label="_xmpps SRV Lookup"]; 7 | xmppsifdot[shape="diamond", label="response = '.'?"]; 8 | xmppsdroprecord[shape="box", style=rounded, label="Drop '.' record"]; 9 | xmppsifempty[shape="diamond", label="len(response) = 0?"]; 10 | xmppsappenddomain[shape="box", style=rounded, label="Use domainpart:5223"]; 11 | 12 | xmppsrv[shape="box", style=rounded, label="_xmpp SRV Lookup"]; 13 | xmppifdot[shape="diamond", label="response = '.'?"]; 14 | xmppdroprecord[shape="box", style=rounded, label="Drop '.' record"]; 15 | xmppifempty[shape="diamond", label="len(response) = 0?"]; 16 | xmppappenddomain[shape="box", style=rounded, label="Use domainpart:5222"]; 17 | 18 | append[shape="box", style=rounded, label="append(xmpps, xmpp)"]; 19 | range[shape="diamond", label="loop records"]; 20 | dialsession[shape="box", style=rounded, label="Connect"]; 21 | 22 | end[shape="oval", label="End"]; 23 | 24 | 25 | dial -> xmppssrv; 26 | xmppssrv -> xmppsifempty; 27 | xmppsifempty -> xmppsappenddomain[label="Yes"]; 28 | xmppsappenddomain -> append; 29 | xmppsifempty -> xmppsifdot[label="No"]; 30 | xmppsifdot:se -> xmppsdroprecord[label="Yes"]; 31 | xmppsdroprecord -> append; 32 | xmppsifdot:sw -> append:w[label="No"]; 33 | 34 | dial -> xmppsrv; 35 | xmppsrv -> xmppifempty[ordering="in"]; 36 | xmppifempty -> xmppappenddomain[label="Yes"]; 37 | xmppappenddomain -> append; 38 | xmppifempty -> xmppifdot[label="No"]; 39 | xmppifdot:sw -> xmppdroprecord[label="Yes"]; 40 | xmppdroprecord -> append; 41 | xmppifdot:se -> append:e[label="No"]; 42 | 43 | append -> range; 44 | 45 | range -> dialsession; 46 | range:se -> end[label="No records left (connect err)"]; 47 | dialsession -> range:sw[label="Failed"]; 48 | dialsession -> end[label="Connected"]; 49 | 50 | {rank=same; xmppssrv xmppsrv} 51 | {rank=same; xmppsifdot xmppsappenddomain xmppappenddomain xmppifdot} 52 | { 53 | // Force the response='.' and add domain on both sides to be symetrical 54 | // using an invisible edge. 55 | rank = same; 56 | edge[ style=invis ]; 57 | xmppappenddomain -> xmppifdot; 58 | rankdir = LR; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/release.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | When making a release, remember to do the following: 4 | 5 | - Create a new commit bumping the release in `CHANGELOG.md` with the commit 6 | message "all: release vX.Y.Z" 7 | - Tag the release with `git tag -s -a --cleanup=whitespace vX.Y.Z` and remove 8 | all comments from the annotation as these won't be treated as comments and 9 | will end up in the final annotation 10 | - Copy the changelog for the release in as the annotation, making sure the 11 | headers are correct (ie. remove one `#' from each header) 12 | - Create a new release on Codeberg (https://codeberg.org/mellium/xmpp/releases) 13 | using the same annotation, making sure headers make sense (remove the top 14 | level one, Codeberg will create that from the tag) 15 | - Do a `go get mellium.im/xmpp@release` (on a machine that has not disabled the 16 | proxy) to trigger the docs being built 17 | - Write up a release announcement on https://opencollective.com/mellium 18 | - Announce the release 19 | - Post it in `users@mellium.chat` 20 | - Post it on fedi at https://social.coop/@mellium 21 | - Post it on Reddit https://www.reddit.com/r/xmpp/ 22 | - If there's anything worth demoing sign up for Office Hours 23 | https://wiki.xmpp.org/web/XMPP_Office_Hours 24 | -------------------------------------------------------------------------------- /docs/xeps.md: -------------------------------------------------------------------------------- 1 | # XEPs 2 | 3 | This library implements a number of XMPP Extension Protocols (XEPs) and RFCs. 4 | Others may be implemented in third party libraries. 5 | The list of XEPs implemented in this module can be found at 6 | https://mellium.im/docs/xeps/. 7 | -------------------------------------------------------------------------------- /error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xmpp 6 | 7 | // Errors used in the SASL package that are needed in tests (but should not be 8 | // exported outside of testing). 9 | var ( 10 | ErrNoMechanisms = errNoMechanisms 11 | ErrUnexpectedPayload = errUnexpectedPayload 12 | ErrTerminated = errTerminated 13 | ) 14 | -------------------------------------------------------------------------------- /examples/commands/.gitignore: -------------------------------------------------------------------------------- 1 | commands 2 | -------------------------------------------------------------------------------- /examples/echobot/.gitignore: -------------------------------------------------------------------------------- 1 | echobot 2 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module mellium.im/xmpp/examples 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.23.1 6 | 7 | require ( 8 | github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592 9 | mellium.im/sasl v0.3.2 10 | mellium.im/xmlstream v0.15.4 11 | mellium.im/xmpp v0.22.0 12 | ) 13 | 14 | require ( 15 | github.com/gdamore/encoding v1.0.1 // indirect 16 | github.com/gdamore/tcell/v2 v2.7.4 // indirect 17 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 18 | github.com/mattn/go-runewidth v0.0.16 // indirect 19 | github.com/rivo/uniseg v0.4.7 // indirect 20 | golang.org/x/crypto v0.31.0 // indirect 21 | golang.org/x/mod v0.22.0 // indirect 22 | golang.org/x/net v0.33.0 // indirect 23 | golang.org/x/sync v0.10.0 // indirect 24 | golang.org/x/sys v0.28.0 // indirect 25 | golang.org/x/term v0.27.0 // indirect 26 | golang.org/x/text v0.21.0 // indirect 27 | golang.org/x/tools v0.28.0 // indirect 28 | mellium.im/reader v0.1.0 // indirect 29 | ) 30 | 31 | replace mellium.im/xmpp => ../ 32 | -------------------------------------------------------------------------------- /examples/im/.gitignore: -------------------------------------------------------------------------------- 1 | im 2 | -------------------------------------------------------------------------------- /examples/msgrepl/.gitignore: -------------------------------------------------------------------------------- 1 | msgrepl 2 | -------------------------------------------------------------------------------- /export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xmpp 6 | 7 | // Error values that have been exported only for tests in the xmpp_test package 8 | // to compare against. 9 | var ( 10 | ErrNotStart = errNotStart 11 | ) 12 | -------------------------------------------------------------------------------- /file/metadata_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package file_test 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | 11 | "mellium.im/xmpp/crypto" 12 | "mellium.im/xmpp/file" 13 | "mellium.im/xmpp/internal/xmpptest" 14 | ) 15 | 16 | var marshalTestCases = []xmpptest.EncodingTestCase{ 17 | 0: { 18 | Value: &file.Meta{ 19 | MediaType: "text/plain", 20 | Date: time.Date(2024, 01, 01, 01, 01, 01, 00, time.UTC), 21 | Hash: crypto.HashOutput{ 22 | Hash: crypto.SHA256, 23 | Out: []byte{1, 2, 3}, 24 | }, 25 | }, 26 | XML: `text/plain2024-01-01T01:01:01Z0AQID000`, 27 | }, 28 | } 29 | 30 | func TestEncode(t *testing.T) { 31 | xmpptest.RunEncodingTests(t, marshalTestCases) 32 | } 33 | -------------------------------------------------------------------------------- /form/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature"; DO NOT EDIT. 2 | 3 | package form 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | -------------------------------------------------------------------------------- /form/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:generate go run ../internal/genfeature 6 | 7 | // Package form implements sending and submitting data forms. 8 | package form // import "mellium.im/xmpp/form" 9 | -------------------------------------------------------------------------------- /form/iter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package form 6 | 7 | // Iter is the interface implemented by types that implement disco form 8 | // extensions. 9 | type Iter interface { 10 | ForForms(node string, f func(*Data) error) error 11 | } 12 | -------------------------------------------------------------------------------- /form/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package form 6 | 7 | // An Field is used to define the behavior and appearance of a data form. 8 | type Field func(*Data) 9 | 10 | // Title sets a form's title. 11 | func Title(s string) Field { 12 | return func(data *Data) { 13 | data.title = s 14 | } 15 | } 16 | 17 | // Instructions adds new textual instructions to the form. 18 | func Instructions(s string) Field { 19 | return func(data *Data) { 20 | data.instructions = s 21 | } 22 | } 23 | 24 | var ( 25 | // Result marks a form as the result type. 26 | // For more information see TypeResult. 27 | Result Field = result 28 | ) 29 | 30 | var ( 31 | result Field = func(data *Data) { 32 | data.typ = TypeResult 33 | } 34 | ) 35 | 36 | // A Option is used to define the behavior and appearance of a form field. 37 | type Option func(*field) 38 | 39 | var ( 40 | // Required flags the field as required in order for the form to be considered 41 | // valid. 42 | Required Option = required 43 | ) 44 | 45 | var ( 46 | required Option = func(f *field) { 47 | f.required = true 48 | } 49 | ) 50 | 51 | // Desc provides a natural-language description of the field, intended for 52 | // presentation in a user-agent (e.g., as a "tool-tip", help button, or 53 | // explanatory text provided near the field). 54 | // Desc should not contain newlines (the \n and \r characters), since layout is 55 | // the responsibility of a user agent. 56 | // However, it does nothing to prevent them from being added. 57 | func Desc(s string) Option { 58 | return func(f *field) { 59 | f.desc = s 60 | } 61 | } 62 | 63 | // Value defines the default value for the field. 64 | // Fields of type ListMulti, JidMulti, TextMulti, and Hidden may contain more 65 | // than one Value; all other field types will only use the first Value. 66 | func Value(s string) Option { 67 | return func(f *field) { 68 | f.value = append(f.value, s) 69 | } 70 | } 71 | 72 | // Label defines a human-readable name for the field. 73 | func Label(s string) Option { 74 | return func(f *field) { 75 | f.label = s 76 | } 77 | } 78 | 79 | // ListItem adds a list item with the provided label and value. 80 | // It has no effect on any non-list field type. 81 | func ListItem(label, value string) Option { 82 | return func(f *field) { 83 | f.option = append(f.option, FieldOpt{ 84 | Label: label, 85 | Value: value, 86 | }) 87 | } 88 | } 89 | 90 | func getFieldOpts(f *field, o ...Option) { 91 | for _, opt := range o { 92 | opt(f) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /forward/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature"; DO NOT EDIT. 2 | 3 | package forward 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mellium.im/xmpp 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | golang.org/x/image v0.23.0 9 | golang.org/x/net v0.39.0 10 | golang.org/x/sys v0.32.0 11 | golang.org/x/text v0.24.0 12 | golang.org/x/tools v0.32.0 13 | mellium.im/sasl v0.3.2 14 | mellium.im/xmlstream v0.15.4 15 | ) 16 | 17 | require ( 18 | golang.org/x/crypto v0.37.0 // indirect 19 | golang.org/x/mod v0.24.0 // indirect 20 | golang.org/x/sync v0.13.0 // indirect 21 | mellium.im/reader v0.1.0 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 4 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 5 | golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= 6 | golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= 7 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 8 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 9 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 10 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 11 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 12 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 13 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 14 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 15 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 16 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 17 | golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= 18 | golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= 19 | mellium.im/reader v0.1.0 h1:UUEMev16gdvaxxZC7fC08j7IzuDKh310nB6BlwnxTww= 20 | mellium.im/reader v0.1.0/go.mod h1:F+X5HXpkIfJ9EE1zHQG9lM/hO946iYAmU7xjg5dsQHI= 21 | mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0= 22 | mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY= 23 | mellium.im/xmlstream v0.15.4 h1:gLKxcWl4rLMUpKgtzrTBvr4OexPeO/edYus+uK3F6ZI= 24 | mellium.im/xmlstream v0.15.4/go.mod h1:yXaCW2++fmVO4L9piKVkyLDqnCmictVYF7FDQW8prb4= 25 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xmpp 6 | 7 | import ( 8 | "encoding/xml" 9 | 10 | "mellium.im/xmlstream" 11 | ) 12 | 13 | // A Handler triggers events or responds to incoming elements in an XML stream. 14 | type Handler interface { 15 | HandleXMPP(t xmlstream.TokenReadEncoder, start *xml.StartElement) error 16 | } 17 | 18 | // The HandlerFunc type is an adapter to allow the use of ordinary functions as 19 | // XMPP handlers. 20 | // If f is a function with the appropriate signature, HandlerFunc(f) is a 21 | // Handler that calls f. 22 | type HandlerFunc func(t xmlstream.TokenReadEncoder, start *xml.StartElement) error 23 | 24 | // HandleXMPP calls f(t, start). 25 | func (f HandlerFunc) HandleXMPP(t xmlstream.TokenReadEncoder, start *xml.StartElement) error { 26 | return f(t, start) 27 | } 28 | -------------------------------------------------------------------------------- /handler_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xmpp_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "errors" 10 | "testing" 11 | 12 | "mellium.im/xmlstream" 13 | "mellium.im/xmpp" 14 | ) 15 | 16 | var errHandlerFuncSentinal = errors.New("handler test") 17 | 18 | type sentinalReadWriter struct{} 19 | 20 | func (sentinalReadWriter) Token() (xml.Token, error) { return nil, nil } 21 | func (sentinalReadWriter) EncodeToken(xml.Token) error { return nil } 22 | func (sentinalReadWriter) Encode(interface{}) error { return nil } 23 | func (sentinalReadWriter) EncodeElement(interface{}, xml.StartElement) error { return nil } 24 | 25 | func TestHandlerFunc(t *testing.T) { 26 | s := &xml.StartElement{} 27 | var f xmpp.HandlerFunc = func(r xmlstream.TokenReadEncoder, start *xml.StartElement) error { 28 | if _, ok := r.(sentinalReadWriter); !ok { 29 | t.Errorf("HandleXMPP did not pass reader to HandlerFunc") 30 | } 31 | if start != s { 32 | t.Errorf("HandleXMPP did not pass start token to HandlerFunc") 33 | } 34 | return errHandlerFuncSentinal 35 | } 36 | 37 | err := f.HandleXMPP(sentinalReadWriter{}, s) 38 | if err != errHandlerFuncSentinal { 39 | t.Errorf("HandleXMPP did not return handlerfunc error, got %q", err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /history/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package history implements fetching messages from an archive. 6 | package history // import "mellium.im/xmpp/history" 7 | 8 | // The namespaces used by this package, provided as a convenience. 9 | const ( 10 | NS = `urn:xmpp:mam:2` 11 | NSExt = `urn:xmpp:mam:2#extended` 12 | ) 13 | -------------------------------------------------------------------------------- /history/fin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package history 6 | 7 | import ( 8 | "encoding/xml" 9 | "strconv" 10 | 11 | "mellium.im/xmlstream" 12 | "mellium.im/xmpp/paging" 13 | ) 14 | 15 | // Result is the metadata (not messages) returned from a MAM query. 16 | type Result struct { 17 | XMLName xml.Name 18 | Complete bool 19 | Unstable bool 20 | Set paging.Set 21 | } 22 | 23 | // TokenReader implements xmlstream.Marshaler. 24 | func (r *Result) TokenReader() xml.TokenReader { 25 | return xmlstream.Wrap( 26 | r.Set.TokenReader(), 27 | xml.StartElement{ 28 | Name: xml.Name{Space: NS, Local: "fin"}, 29 | Attr: []xml.Attr{{ 30 | Name: xml.Name{Local: "complete"}, 31 | Value: strconv.FormatBool(r.Complete), 32 | }, { 33 | Name: xml.Name{Local: "stable"}, 34 | Value: strconv.FormatBool(!r.Unstable), 35 | }}, 36 | }, 37 | ) 38 | } 39 | 40 | // WriteXML implements xmlstream.WriterTo. 41 | func (r *Result) WriteXML(w xmlstream.TokenWriter) (int, error) { 42 | return xmlstream.Copy(w, r.TokenReader()) 43 | } 44 | 45 | // MarshalXML implements xml.Marshaler. 46 | func (r *Result) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { 47 | _, err := r.WriteXML(e) 48 | return err 49 | } 50 | 51 | // UnmarshalXML implements xml.Unmarshaler. 52 | func (r *Result) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 53 | var foundComplete, foundStable bool 54 | for _, attr := range start.Attr { 55 | switch attr.Name.Local { 56 | case "complete": 57 | r.Complete = attr.Value == "true" 58 | foundComplete = true 59 | case "stable": 60 | r.Unstable = attr.Value == "false" 61 | foundStable = true 62 | } 63 | if foundComplete && foundStable { 64 | break 65 | } 66 | } 67 | var set paging.Set 68 | tok, err := d.Token() 69 | if err != nil { 70 | return err 71 | } 72 | start, ok := tok.(xml.StartElement) 73 | if !ok { 74 | return nil 75 | } 76 | err = d.DecodeElement(&set, &start) 77 | if err != nil { 78 | return err 79 | } 80 | r.Set = set 81 | return d.Skip() 82 | } 83 | -------------------------------------------------------------------------------- /history/fin_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package history_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "testing" 10 | 11 | "mellium.im/xmlstream" 12 | "mellium.im/xmpp/history" 13 | "mellium.im/xmpp/internal/xmpptest" 14 | "mellium.im/xmpp/paging" 15 | ) 16 | 17 | var ( 18 | _ xml.Unmarshaler = (*history.Query)(nil) 19 | _ xml.Marshaler = (*history.Query)(nil) 20 | _ xmlstream.Marshaler = (*history.Query)(nil) 21 | _ xmlstream.WriterTo = (*history.Query)(nil) 22 | ) 23 | 24 | var resEncodingTestCases = []xmpptest.EncodingTestCase{ 25 | 0: { 26 | Value: &history.Result{ 27 | Set: paging.Set{ 28 | XMLName: xml.Name{Space: paging.NS, Local: "set"}, 29 | }, 30 | }, 31 | XML: ``, 32 | }, 33 | } 34 | 35 | func TestEncodeResult(t *testing.T) { 36 | xmpptest.RunEncodingTests(t, resEncodingTestCases) 37 | } 38 | -------------------------------------------------------------------------------- /history/iter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package history 6 | 7 | import ( 8 | "encoding/xml" 9 | ) 10 | 11 | // Iter is an iterator over message history. 12 | type Iter struct { 13 | err error 14 | msgC chan xml.TokenReader 15 | cur xml.TokenReader 16 | h *Handler 17 | id string 18 | res Result 19 | } 20 | 21 | // Next advances the iterator 22 | func (i *Iter) Next() bool { 23 | var ok bool 24 | i.cur, ok = <-i.msgC 25 | return ok 26 | } 27 | 28 | // Current returns the current message stream read from the iterator. 29 | func (i *Iter) Current() xml.TokenReader { 30 | return i.cur 31 | } 32 | 33 | // Err returns any error encountered by the iterator. 34 | func (i *Iter) Err() error { 35 | return i.err 36 | } 37 | 38 | // Result contains the results of the query after iteration has completed if no 39 | // error was encountered. 40 | func (i *Iter) Result() Result { 41 | return i.res 42 | } 43 | 44 | // Close stops iterating over this query. 45 | // Future messages will still be received but will be handled by the fallback 46 | // handler instead. 47 | func (i *Iter) Close() error { 48 | i.h.remove(i.id) 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /ibb/ibb_internal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package ibb 6 | 7 | import ( 8 | "context" 9 | 10 | "mellium.im/xmpp/jid" 11 | ) 12 | 13 | // WaitExpect blocks until Expect has been called for the given JID/SID 14 | // combination. 15 | // 16 | // This method is only available in tests and is a work around for issue #407. 17 | // It's not ideal that we have to mess with the internals of the listener, but 18 | // it was the only way I could think of to make sure that expect was actually 19 | // listening before sending the IQ. 20 | // 21 | // DANGER: 22 | // If you are modifying this test, make sure that this function does not modify 23 | // any internal state of the listener. 24 | // We want to be testing the actual code as it will be run and not have tests 25 | // messing with things. 26 | func (l *Listener) WaitExpect(ctx context.Context, from jid.JID, sid string) error { 27 | key := from.String() + ":" + sid 28 | for { 29 | select { 30 | case <-ctx.Done(): 31 | return ctx.Err() 32 | default: 33 | l.eLock.Lock() 34 | _, ok := l.expected[key] 35 | l.eLock.Unlock() 36 | if ok { 37 | return nil 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ibb/listen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package ibb 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "net" 11 | "sync" 12 | 13 | "mellium.im/xmpp" 14 | "mellium.im/xmpp/jid" 15 | ) 16 | 17 | type expected struct { 18 | c chan *Conn 19 | cancel context.CancelFunc 20 | } 21 | 22 | // Listener is an implementation of net.Listener that is used to accept 23 | // incoming IBB streams. 24 | type Listener struct { 25 | s *xmpp.Session 26 | h *Handler 27 | c chan *Conn 28 | expected map[string]expected 29 | eLock sync.Mutex 30 | } 31 | 32 | // Accept waits for the next incoming IBB stream and returns the connection. 33 | // If the listener is closed by either end pending Accept calls unblock and 34 | // return an error. 35 | func (l *Listener) Accept() (net.Conn, error) { 36 | conn, ok := <-l.c 37 | if !ok { 38 | return nil, errors.New("ibb: accept on closed listener") 39 | } 40 | return conn, nil 41 | } 42 | 43 | // Expect is like Accept except that it accepts a specific session that has been 44 | // negotiated out-of-band. 45 | // If Accept and Expect are both waiting on connections, Expect will take 46 | // precedence. 47 | // If Expect is called twice for the same session the original call will be 48 | // canceled and return a context error and the new Expect call will take over. 49 | func (l *Listener) Expect(ctx context.Context, from jid.JID, sid string) (net.Conn, error) { 50 | l.eLock.Lock() 51 | if l.expected == nil { 52 | l.expected = make(map[string]expected) 53 | } 54 | key := from.String() + ":" + sid 55 | e, ok := l.expected[key] 56 | if ok { 57 | e.cancel() 58 | } 59 | e.c = make(chan *Conn) 60 | ctx, cancel := context.WithCancel(ctx) 61 | e.cancel = cancel 62 | l.expected[key] = e 63 | l.eLock.Unlock() 64 | 65 | select { 66 | case <-ctx.Done(): 67 | return nil, ctx.Err() 68 | case conn, ok := <-e.c: 69 | if !ok { 70 | return nil, errors.New("ibb: accept on closed listener") 71 | } 72 | return conn, nil 73 | } 74 | } 75 | 76 | // Close stops listening and causes any pending Accept calls to unblock and 77 | // return an error. 78 | // Already accepted connections are not closed. 79 | func (l *Listener) Close() error { 80 | l.h.lM.Lock() 81 | defer l.h.lM.Unlock() 82 | delete(l.h.l, l.s.LocalAddr().String()) 83 | close(l.c) 84 | return nil 85 | } 86 | 87 | // Addr returns the local address for which this listener is accepting 88 | // connections. 89 | func (l *Listener) Addr() net.Addr { 90 | return l.s.LocalAddr() 91 | } 92 | -------------------------------------------------------------------------------- /internal/attr/attr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package attr 6 | 7 | import ( 8 | "encoding/xml" 9 | ) 10 | 11 | // Get returns the value and index of the first attribute with the provided 12 | // local name from a list of attributes or -1 and an empty string if no such 13 | // attribute exists. 14 | func Get(attr []xml.Attr, local string) (int, string) { 15 | for idx, a := range attr { 16 | if a.Name.Local == local { 17 | return idx, a.Value 18 | } 19 | } 20 | return -1, "" 21 | } 22 | -------------------------------------------------------------------------------- /internal/attr/attr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package attr_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "strconv" 10 | "testing" 11 | 12 | "mellium.im/xmpp/internal/attr" 13 | ) 14 | 15 | var attrTests = [...]struct { 16 | attr []xml.Attr 17 | local string 18 | out string 19 | idx int 20 | }{ 21 | 0: {idx: -1}, 22 | 1: {idx: -1, local: "test"}, 23 | 2: {idx: -1, attr: []xml.Attr{}}, 24 | 3: {idx: -1, attr: []xml.Attr{}, local: "test"}, 25 | 4: { 26 | attr: []xml.Attr{{Name: xml.Name{Local: "test"}, Value: "test"}}, 27 | local: "test", 28 | out: "test", 29 | }, 30 | 5: { 31 | attr: []xml.Attr{ 32 | {Name: xml.Name{Local: "test"}, Value: "test0"}, 33 | {Name: xml.Name{Local: "test"}, Value: "test1"}, 34 | }, 35 | local: "test", 36 | out: "test0", 37 | }, 38 | 6: { 39 | attr: []xml.Attr{ 40 | {Name: xml.Name{Local: "a"}, Value: "test0"}, 41 | {Name: xml.Name{Local: "b"}, Value: "test1"}, 42 | }, 43 | local: "b", 44 | out: "test1", 45 | idx: 1, 46 | }, 47 | } 48 | 49 | func TestAttr(t *testing.T) { 50 | for i, tc := range attrTests { 51 | t.Run(strconv.Itoa(i), func(t *testing.T) { 52 | idx, out := attr.Get(tc.attr, tc.local) 53 | if out != tc.out { 54 | t.Errorf("Wrong output: want=%q, got=%q", tc.out, out) 55 | } 56 | if idx != tc.idx { 57 | t.Errorf("Wrong index: want=%d, got=%d", tc.idx, idx) 58 | } 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /internal/attr/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package attr contains unexported functionality related to XML attributes. 6 | package attr // import "mellium.im/xmpp/internal/attr" 7 | -------------------------------------------------------------------------------- /internal/attr/idgen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package attr 6 | 7 | import ( 8 | "crypto/rand" 9 | "fmt" 10 | "io" 11 | ) 12 | 13 | // IDLen is the standard length of stanza identifiers in bytes. 14 | const IDLen = 16 15 | 16 | // RandomID generates a new random identifier of length IDLen. If the OS's 17 | // entropy pool isn't initialized, or we can't generate random numbers for some 18 | // other reason, panic. 19 | func RandomID() string { 20 | return randomID(IDLen, rand.Reader) 21 | } 22 | 23 | // RandomLen is like RandomID but the length is configurable. 24 | func RandomLen(n int) string { 25 | return randomID(n, rand.Reader) 26 | } 27 | 28 | func randomID(n int, r io.Reader) string { 29 | b := make([]byte, (n/2)+(n&1)) 30 | switch n, err := r.Read(b); { 31 | case err != nil: 32 | panic(err) 33 | case n != len(b): 34 | panic("Could not read enough randomness") 35 | } 36 | 37 | return fmt.Sprintf("%x", b)[:n] 38 | } 39 | -------------------------------------------------------------------------------- /internal/attr/idgen_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package attr 6 | 7 | import ( 8 | "errors" 9 | "testing" 10 | ) 11 | 12 | func TestPublicRandomIDLength(t *testing.T) { 13 | if s := RandomID(); len(s) != IDLen { 14 | t.Errorf("Expected length %d got %d", IDLen, len(s)) 15 | } 16 | } 17 | 18 | type zeroReader struct{} 19 | 20 | func (z zeroReader) Read(b []byte) (n int, err error) { 21 | for i := range b { 22 | b[i] = 0 23 | } 24 | 25 | return len(b), nil 26 | } 27 | 28 | func TestRandomIDLength(t *testing.T) { 29 | for i := 0; i <= 15; i++ { 30 | if s := randomID(i, zeroReader{}); len(s) != i { 31 | t.Errorf("Expected length %d got %d", i, len(s)) 32 | } 33 | } 34 | } 35 | 36 | type errorReader struct{} 37 | 38 | func (errorReader) Read(p []byte) (int, error) { 39 | return 0, errors.New("Expected error from error reader") 40 | } 41 | 42 | type nopReader struct{} 43 | 44 | func (nopReader) Read(p []byte) (int, error) { 45 | return 0, nil 46 | } 47 | 48 | func TestRandomPanicsIfRandReadFails(t *testing.T) { 49 | defer func() { 50 | if r := recover(); r == nil { 51 | t.Error("Expected randomID to panic if reading random bytes failed") 52 | } 53 | }() 54 | randomID(16, errorReader{}) 55 | } 56 | 57 | func TestRandomPanicsIfRandReadWrongLen(t *testing.T) { 58 | defer func() { 59 | if r := recover(); r == nil { 60 | t.Error("Expected randomID to panic if no random bytes were read") 61 | } 62 | }() 63 | randomID(16, nopReader{}) 64 | } 65 | -------------------------------------------------------------------------------- /internal/cid/cid.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package cid implements content-ID URLs. 6 | package cid // import "mellium.im/xmpp/internal/cid" 7 | 8 | import ( 9 | "encoding/hex" 10 | "errors" 11 | "fmt" 12 | "net/url" 13 | ) 14 | 15 | // Parse parses a raw url into a URL structure. 16 | // 17 | // The URL may or may not include the cid: scheme, but will fail if the exact 18 | // structure for CID URLs is not met (or if the scheme is present and is 19 | // anything other than "cid"). 20 | func Parse(rawURL string) (*URL, error) { 21 | u, err := url.Parse(rawURL) 22 | if err != nil { 23 | return nil, err 24 | } 25 | var opaque string 26 | switch u.Scheme { 27 | case "": 28 | opaque = u.Path 29 | case "cid": 30 | opaque = u.Opaque 31 | default: 32 | return nil, fmt.Errorf("cid: failed to parse URL with scheme %q", u.Scheme) 33 | } 34 | if opaque == "" { 35 | return nil, errors.New("cid: URL is invalid and resulted in empty CID") 36 | } 37 | var plusIdx, atIdx int 38 | opaque: 39 | for i, b := range opaque { 40 | switch b { 41 | case '+': 42 | plusIdx = i 43 | case '@': 44 | if plusIdx <= 0 { 45 | return nil, errors.New("cid: missing hash name") 46 | } 47 | atIdx = i 48 | break opaque 49 | } 50 | } 51 | if atIdx <= 0 || atIdx == len(opaque)-1 { 52 | return nil, errors.New("cid: missing domain part") 53 | } 54 | 55 | out := &URL{ 56 | HashName: opaque[:plusIdx], 57 | Domain: opaque[atIdx+1:], 58 | } 59 | hash, err := hex.DecodeString(opaque[plusIdx+1 : atIdx]) 60 | if err != nil { 61 | return nil, fmt.Errorf("cid: error decoding hash: %w", err) 62 | } 63 | out.Hash = hash 64 | if len(hash) == 0 { 65 | return nil, errors.New("cid: no hash found") 66 | } 67 | 68 | return out, nil 69 | } 70 | 71 | // A URL represents a parsed CID URL. 72 | type URL struct { 73 | HashName string 74 | Domain string 75 | Hash []byte 76 | } 77 | 78 | // String reassembles the URL into a valid URL string. 79 | func (u *URL) String() string { 80 | return fmt.Sprintf("cid:%s+%x@%s", u.HashName, u.Hash, u.Domain) 81 | } 82 | -------------------------------------------------------------------------------- /internal/decl/decl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package decl contains functionality related to XML declarations. 6 | package decl // import "mellium.im/xmpp/internal/decl" 7 | 8 | import ( 9 | "encoding/xml" 10 | ) 11 | 12 | const ( 13 | // XMLHeader is an XML header like the one in encoding/xml but without a 14 | // newline at the end. 15 | XMLHeader = `` 16 | ) 17 | 18 | type skipper struct { 19 | r xml.TokenReader 20 | started bool 21 | } 22 | 23 | func (r *skipper) Token() (xml.Token, error) { 24 | tok, err := r.r.Token() 25 | if tok != nil && !r.started { 26 | r.started = true 27 | if proc, ok := tok.(xml.ProcInst); ok && proc.Target == "xml" { 28 | if err != nil { 29 | return nil, err 30 | } 31 | return r.r.Token() 32 | } 33 | } 34 | return tok, err 35 | } 36 | 37 | // Skip wraps a token reader and skips any XML declaration. 38 | func Skip(r xml.TokenReader) xml.TokenReader { 39 | return &skipper{r: r} 40 | } 41 | 42 | type trimmer struct { 43 | r xml.TokenReader 44 | foundStart bool 45 | } 46 | 47 | func (t *trimmer) Token() (xml.Token, error) { 48 | tok, err := t.r.Token() 49 | if t.foundStart || tok == nil { 50 | return tok, err 51 | } 52 | switch char := tok.(type) { 53 | case xml.StartElement: 54 | t.foundStart = true 55 | return tok, err 56 | case xml.CharData: 57 | for _, c := range char { 58 | if c != ' ' && c != '\n' && c != '\r' && c != '\t' { 59 | return tok, err 60 | } 61 | } 62 | if err != nil { 63 | return nil, err 64 | } 65 | return t.Token() 66 | } 67 | return tok, err 68 | } 69 | 70 | // TrimLeftSpace is a transformer that removes all whitespace only chardata 71 | // tokens found before the next StartElement token (and then returns the stream 72 | // normally past that point). 73 | func TrimLeftSpace(r xml.TokenReader) xml.TokenReader { 74 | return &trimmer{r: r} 75 | } 76 | -------------------------------------------------------------------------------- /internal/decl/decl_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package decl_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "io" 10 | "testing" 11 | 12 | "mellium.im/xmlstream" 13 | "mellium.im/xmpp/internal/decl" 14 | "mellium.im/xmpp/internal/xmpptest" 15 | ) 16 | 17 | var skipTests = []xmpptest.TransformerTestCase{ 18 | 0: {}, 19 | 1: {In: "", Out: ""}, 20 | 2: {In: xml.Header + "", Out: "\n"}, 21 | 3: {In: ``, Out: ""}, 22 | 4: {In: ``, Out: ""}, 23 | 5: {In: ``}, 24 | } 25 | 26 | func TestDecl(t *testing.T) { 27 | xmpptest.RunTransformerTests(t, decl.Skip, skipTests) 28 | } 29 | 30 | func TestImmediateEOF(t *testing.T) { 31 | d := decl.Skip(xmlstream.Token(xml.ProcInst{Target: "xml"})) 32 | 33 | for i := 0; i < 2; i++ { 34 | tok, err := d.Token() 35 | if err != io.EOF { 36 | t.Errorf("Expected EOF on %d but got %q", i, err) 37 | } 38 | if tok != nil { 39 | t.Errorf("Did not expect token on %d but got %T %[2]v", i, tok) 40 | } 41 | } 42 | } 43 | 44 | var trimSpaceTests = []xmpptest.TransformerTestCase{ 45 | 0: {}, 46 | 1: {In: "\t\n\r ", Out: ""}, 47 | 2: {In: "a b", Out: "a b"}, 48 | 3: {In: "\n\n", Out: "\n"}, 49 | 4: {In: " \n a b", Out: " \n a b"}, 50 | 5: {In: " \n ", Out: ""}, 51 | 6: {InStream: xmlstream.ReaderFunc(func() (xml.Token, error) { 52 | // Concurrent EOF should also be trimmed. 53 | return xml.CharData(" \n"), io.EOF 54 | }), Out: ""}, 55 | } 56 | 57 | func TestTrimSpace(t *testing.T) { 58 | xmpptest.RunTransformerTests(t, decl.TrimLeftSpace, trimSpaceTests) 59 | } 60 | -------------------------------------------------------------------------------- /internal/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package internal provides non-exported functionality used by xmpp and its 6 | // child packages. 7 | package internal // import "mellium.im/xmpp/internal" 8 | -------------------------------------------------------------------------------- /internal/integration/aioxmpp/aioxmpp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package aioxmpp facilitates integration testing against aioxmpp. 6 | // 7 | // Tests are written by importing the automatically generated aioxmpp_client 8 | // Python package, subclassing the Daemon class, and overriding its run method. 9 | // Other methods can also be overridden, in particular the prepare_argparse 10 | // method can be used to add command line arguments to the Python scripts. 11 | // For example: 12 | // 13 | // from aioxmpp_client import Daemon 14 | // import aioxmpp 15 | // 16 | // 17 | // class Ping(Daemon): 18 | // def prepare_argparse(self) -> None: 19 | // super().prepare_argparse() 20 | // 21 | // def jid(s): 22 | // return aioxmpp.JID.fromstr(s) 23 | // self.argparse.add_argument( 24 | // "-j", 25 | // type=jid, 26 | // help="The JID to ping", 27 | // ) 28 | // 29 | // async def run(self) -> None: 30 | // await aioxmpp.ping.ping(self.client, self.args.j) 31 | // 32 | // For more information see aioxmpp_client.py, python/xmpptest.py, and the 33 | // aioxmpp documentation. 34 | package aioxmpp // import "mellium.im/xmpp/internal/integration/aioxmpp" 35 | 36 | import ( 37 | "context" 38 | _ "embed" 39 | "io" 40 | "testing" 41 | "text/template" 42 | 43 | "mellium.im/xmpp/internal/integration" 44 | "mellium.im/xmpp/internal/integration/python" 45 | ) 46 | 47 | const ( 48 | baseFileName = "aioxmpp_client.py" 49 | ) 50 | 51 | var ( 52 | //go:embed aioxmpp_client.py 53 | baseTest string 54 | ) 55 | 56 | func getConfig(cmd *integration.Cmd) python.Config { 57 | if cmd.Config == nil { 58 | cmd.Config = python.Config{} 59 | } 60 | return cmd.Config.(python.Config) 61 | } 62 | 63 | func defaultConfig(cmd *integration.Cmd) error { 64 | tmpl, err := template.New("aioxmpp").Parse(baseTest) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | err = integration.Name("aioxmpp")(cmd) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | return integration.TempFile(baseFileName, func(cmd *integration.Cmd, w io.Writer) error { 75 | cfg := getConfig(cmd) 76 | return tmpl.Execute(w, cfg) 77 | })(cmd) 78 | } 79 | 80 | // Test starts the aioxmpp wrapper script and returns a function that runs 81 | // subtests using t.Run. 82 | // Multiple calls to the returned function will result in uniquely named 83 | // subtests. 84 | // When all subtests have completed, the daemon is stopped. 85 | func Test(ctx context.Context, t *testing.T, opts ...integration.Option) integration.SubtestRunner { 86 | opts = append(opts, defaultConfig) 87 | return python.Test(ctx, t, opts...) 88 | } 89 | -------------------------------------------------------------------------------- /internal/integration/aioxmpp/aioxmpp_client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Mellium Contributors. 2 | # Use of this source code is governed by the BSD 2-clause 3 | # license that can be found in the LICENSE file. 4 | 5 | from aioxmpp import connector 6 | from aioxmpp import JID 7 | from aioxmpp import security_layer 8 | import aioxmpp 9 | import xmpptest 10 | 11 | ### 12 | # This script acts as a wrapper around aioxmpp that starts a simple client. 13 | # It is not meant to do any testing itself, instead use the Go python.Import 14 | # option to create a test script that subclasses Daemon and overrides its run 15 | # method. 16 | ### 17 | 18 | 19 | class Daemon(xmpptest.Daemon): 20 | def __init__(self) -> None: 21 | super().__init__() 22 | 23 | def configure(self): 24 | super().configure() 25 | security: security_layer.SecurityLayer = aioxmpp.make_security_layer( 26 | self.config.get("client", "password"), 27 | no_verify=True, 28 | ) 29 | self.jid: JID = JID.fromstr(self.config.get("client", "jid")) 30 | xmpp_port = self.config.getint("client", "port") 31 | conn: connector.BaseConnector = connector.STARTTLSConnector() 32 | self.client: aioxmpp.Client = aioxmpp.PresenceManagedClient( 33 | self.jid, 34 | security, 35 | override_peer=[("127.0.0.1", xmpp_port, conn)], 36 | ) 37 | 38 | async def run_test(self) -> None: 39 | async with self.client.connected(): 40 | await self.run() 41 | 42 | async def run(self) -> None: 43 | """ 44 | The run method should be overridden by the test script and perform any 45 | actions required for testing such as listening for incoming messages and 46 | responding using self.client. 47 | """ 48 | raise NotImplementedError("test file must override run function") 49 | -------------------------------------------------------------------------------- /internal/integration/jackal/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package jackal 6 | 7 | import ( 8 | _ "embed" 9 | "path/filepath" 10 | "text/template" 11 | ) 12 | 13 | // Config contains options that can be written to a Jackal config file. 14 | type Config struct { 15 | C2SPort int 16 | AdminPort int 17 | HTTPPort int 18 | VHosts []string 19 | Modules []string 20 | } 21 | 22 | var ( 23 | //go:embed config.yml.tmpl 24 | cfgBase string 25 | 26 | cfgTmpl = template.Must(template.New("cfg").Funcs(template.FuncMap{ 27 | "filepathJoin": filepath.Join, 28 | }).Parse(cfgBase)) 29 | ) 30 | -------------------------------------------------------------------------------- /internal/integration/jackal/config.yml.tmpl: -------------------------------------------------------------------------------- 1 | peppers: 2 | keys: 3 | v1: integration-tests-not-secret 4 | use: v1 5 | 6 | http: 7 | port: {{ .HTTPPort }} 8 | 9 | logger: 10 | level: "debug" 11 | 12 | storage: 13 | type: boltdb 14 | boltdb: 15 | path: "jackal.db" 16 | 17 | admin: 18 | port: {{ .AdminPort }} 19 | 20 | {{ range $idx, $vhost := .VHosts }} 21 | {{ if eq 0 $idx }} 22 | hosts: 23 | {{ end }} 24 | - domain: {{ $vhost }} 25 | tls: 26 | cert_file: "{{ $vhost }}.crt" 27 | privkey_file: "{{ $vhost }}.key" 28 | {{ end }} 29 | 30 | shapers: 31 | - name: normal 32 | max_sessions: 10 33 | rate: 34 | limit: 65536 35 | burst: 32768 36 | 37 | {{ if .C2SPort }} 38 | c2s: 39 | listeners: 40 | - port: {{ .C2SPort }} 41 | req_timeout: 60s 42 | transport: socket 43 | sasl: 44 | mechanisms: 45 | - scram_sha_1 46 | - scram_sha_256 47 | {{ end }} 48 | 49 | {{- if .Modules }} 50 | modules: 51 | enabled: 52 | {{- range .Modules }} 53 | - {{ . }} 54 | {{- end }} 55 | {{- end }} 56 | 57 | version: 58 | show_os: true 59 | 60 | offline: 61 | queue_size: 300 62 | 63 | ping: 64 | ack_timeout: 90s 65 | interval: 3m 66 | send_pings: true 67 | timeout_action: kill 68 | -------------------------------------------------------------------------------- /internal/integration/mellium/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package mellium 6 | 7 | import ( 8 | "mellium.im/xmpp" 9 | ) 10 | 11 | // Config contains options that can be used to configure the newly started 12 | // server. 13 | type Config struct { 14 | ListenC2S bool 15 | ListenS2S bool 16 | C2SFeatures []xmpp.StreamFeature 17 | S2SFeatures []xmpp.StreamFeature 18 | LogXML bool 19 | } 20 | -------------------------------------------------------------------------------- /internal/integration/prosody/LICENSE.modules: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2009-2015 Various Contributors (see individual files and source control) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /internal/integration/prosody/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package prosody 6 | 7 | import ( 8 | _ "embed" 9 | "fmt" 10 | "path/filepath" 11 | "strings" 12 | "text/template" 13 | ) 14 | 15 | // Config contains options that can be written to a Prosody config file. 16 | type Config struct { 17 | C2SPort int 18 | S2SPort int 19 | CompPort int 20 | HTTPPort int 21 | HTTPSPort int 22 | Admins []string 23 | Modules []string 24 | VHosts []string 25 | Options map[string]interface{} 26 | Component map[string]struct { 27 | Name string 28 | Secret string 29 | Modules []string 30 | MUCDefaults []ChannelConfig 31 | } 32 | } 33 | 34 | // ChannelConfig configures a Multi-User Chat channel. 35 | type ChannelConfig struct { 36 | Localpart string 37 | Admins []string 38 | Owners []string 39 | Visitors []string 40 | Name string 41 | Desc string 42 | AllowMemberInvites bool 43 | ChangeSubject bool 44 | HistoryLen int 45 | Lang string 46 | Pass string 47 | Logging bool 48 | MembersOnly bool 49 | Moderated bool 50 | Persistent bool 51 | Public bool 52 | PublicJIDs bool 53 | } 54 | 55 | var ( 56 | //go:embed prosody.cfg.lua.tmpl 57 | cfgBase string 58 | 59 | cfgTmpl = template.Must(template.New("cfg").Funcs(template.FuncMap{ 60 | "filepathJoin": filepath.Join, 61 | "joinQuote": func(s []string) string { 62 | s = append(s[:0:0], s...) 63 | for i, ss := range s { 64 | s[i] = fmt.Sprintf("%q", ss) 65 | } 66 | return strings.Join(s, ",") 67 | }, 68 | "luaList": func(s []string) string { 69 | s = append(s[:0:0], s...) 70 | for i, ss := range s { 71 | s[i] = fmt.Sprintf("%q", ss) 72 | } 73 | var end string 74 | if len(s) > 0 { 75 | end = ";\n" 76 | } 77 | return strings.Join(s, ";\n") + end 78 | }, 79 | "quoteOrPrint": func(v interface{}) string { 80 | switch vv := v.(type) { 81 | case string: 82 | return fmt.Sprintf("%q", vv) 83 | default: 84 | return fmt.Sprintf("%v", vv) 85 | } 86 | }, 87 | }).Parse(cfgBase)) 88 | ) 89 | -------------------------------------------------------------------------------- /internal/integration/prosody/mod_muc_defaults.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package prosody 6 | 7 | import ( 8 | _ "embed" 9 | "io" 10 | 11 | "mellium.im/xmpp/internal/integration" 12 | ) 13 | 14 | //go:embed mod_muc_defaults.lua 15 | var modMUCDefaults []byte 16 | 17 | // Channel configures the MUC component (if loaded) with a default channel or 18 | // channels. 19 | func Channel(domain string, c ...ChannelConfig) integration.Option { 20 | const modName = "muc_defaults" 21 | return func(cmd *integration.Cmd) error { 22 | cfg := getConfig(cmd) 23 | comp := cfg.Component[domain] 24 | comp.MUCDefaults = append(comp.MUCDefaults, c...) 25 | comp.Modules = append(comp.Modules, modName) 26 | cfg.Component[domain] = comp 27 | cmd.Config = cfg 28 | return integration.TempFile("mod_"+modName+".lua", func(_ *integration.Cmd, w io.Writer) error { 29 | _, err := w.Write(modMUCDefaults) 30 | return err 31 | })(cmd) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/integration/prosody/mod_trustall.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package prosody 6 | 7 | import ( 8 | _ "embed" 9 | "io" 10 | 11 | "mellium.im/xmpp/internal/integration" 12 | ) 13 | 14 | //go:embed mod_trustall.lua 15 | var modTrustAll []byte 16 | 17 | // TrustAll configures prosody to trust all certificates presented to it without 18 | // any verification. 19 | func TrustAll() integration.Option { 20 | const modName = "trustall" 21 | return func(cmd *integration.Cmd) error { 22 | err := Modules(modName)(cmd) 23 | if err != nil { 24 | return err 25 | } 26 | return integration.TempFile("mod_"+modName+".lua", func(_ *integration.Cmd, w io.Writer) error { 27 | _, err := w.Write(modTrustAll) 28 | return err 29 | })(cmd) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/integration/prosody/mod_trustall.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2022 The Mellium Contributors. 2 | -- Use of this source code is governed by the BSD 2-clause 3 | -- license that can be found in the LICENSE file. 4 | 5 | module:set_global(); 6 | 7 | module:hook("s2s-check-certificate", function(event) 8 | local session = event.session; 9 | module:log("info", "implicitly trusting presented certificate"); 10 | session.cert_chain_status = "valid"; 11 | session.cert_identity_status = "valid"; 12 | return true; 13 | end); 14 | -------------------------------------------------------------------------------- /internal/integration/python/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package python 6 | 7 | import ( 8 | _ "embed" 9 | "io" 10 | "text/template" 11 | 12 | "mellium.im/xmpp/internal/integration" 13 | "mellium.im/xmpp/jid" 14 | ) 15 | 16 | const ( 17 | baseModule = "xmpptest" 18 | baseFileName = baseModule + ".py" 19 | cfgFileName = "python_config.ini" 20 | cfgFlag = "-c" 21 | ) 22 | 23 | var ( 24 | //go:embed python_config.ini 25 | cfgBase string 26 | 27 | //go:embed xmpptest.py 28 | baseTest string 29 | ) 30 | 31 | // Config contains options that can be written to the config file. 32 | type Config struct { 33 | JID jid.JID 34 | Password string 35 | Port string 36 | Imports [][]string 37 | Args []string 38 | } 39 | 40 | // ConfigFile is an option that can be used to write a temporary config file. 41 | // It is used to pass the connection parameters to the Python side of the tests. 42 | func ConfigFile(cfg Config) integration.Option { 43 | cfgTmpl := template.Must(template.New("cfg").Parse(cfgBase)) 44 | 45 | return func(cmd *integration.Cmd) error { 46 | cmd.Config = cfg 47 | return integration.TempFile(cfgFileName, func(cmd *integration.Cmd, w io.Writer) error { 48 | return cfgTmpl.Execute(w, cfg) 49 | })(cmd) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/integration/python/python.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package python facilitates integration testing against Python scripts. 6 | package python // import "mellium.im/xmpp/internal/integration/python" 7 | 8 | import ( 9 | "context" 10 | "io" 11 | "testing" 12 | "text/template" 13 | 14 | "mellium.im/xmpp/internal/attr" 15 | "mellium.im/xmpp/internal/integration" 16 | ) 17 | 18 | const cmdName = "python" 19 | 20 | func getConfig(cmd *integration.Cmd) Config { 21 | if cmd.Config == nil { 22 | cmd.Config = Config{} 23 | } 24 | return cmd.Config.(Config) 25 | } 26 | 27 | func defaultConfig(cmd *integration.Cmd) error { 28 | tmpl, err := template.New("python").Parse(baseTest) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | err = integration.TempFile(baseFileName, func(cmd *integration.Cmd, w io.Writer) error { 34 | cfg := getConfig(cmd) 35 | return tmpl.Execute(w, cfg) 36 | })(cmd) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | cfg := getConfig(cmd) 42 | return integration.Args(append([]string{"-m", baseModule}, cfg.Args...)...)(cmd) 43 | } 44 | 45 | // Import causes the given script to be written out to the working directory and 46 | // the class name in that script to be imported by the main test runner and 47 | // executed. 48 | func Import(class, script string) integration.Option { 49 | return func(cmd *integration.Cmd) error { 50 | fName := "tmp_" + attr.RandomID() 51 | cfg := getConfig(cmd) 52 | cfg.Imports = append(cfg.Imports, []string{fName, class}) 53 | cmd.Config = cfg 54 | return integration.TempFile(fName+".py", func(cmd *integration.Cmd, w io.Writer) error { 55 | _, err := io.WriteString(w, script) 56 | return err 57 | })(cmd) 58 | } 59 | } 60 | 61 | // Args sets additional command line args to be passed to the script (ie. after 62 | // the script name). 63 | // If you want to pass arguments to the python process (before the script name) 64 | // use integration.Args. 65 | func Args(f ...string) integration.Option { 66 | return func(cmd *integration.Cmd) error { 67 | cfg := getConfig(cmd) 68 | cfg.Args = append(cfg.Args, f...) 69 | cmd.Config = cfg 70 | return nil 71 | } 72 | } 73 | 74 | // Test starts a Python script and returns a function that runs subtests using 75 | // t.Run. 76 | // Multiple calls to the returned function will result in uniquely named 77 | // subtests. 78 | // When all subtests have completed, the daemon is stopped. 79 | func Test(ctx context.Context, t *testing.T, opts ...integration.Option) integration.SubtestRunner { 80 | opts = append(opts, defaultConfig) 81 | return integration.Test(ctx, cmdName, t, opts...) 82 | } 83 | -------------------------------------------------------------------------------- /internal/integration/python/python_config.ini: -------------------------------------------------------------------------------- 1 | [client] 2 | jid = {{.JID}} 3 | password = {{.Password}} 4 | port = {{.Port}} 5 | -------------------------------------------------------------------------------- /internal/integration/python/xmpptest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2022 The Mellium Contributors. 3 | # Use of this source code is governed by the BSD 2-clause 4 | # license that can be found in the LICENSE file. 5 | 6 | import abc 7 | import argparse 8 | import asyncio 9 | import configparser 10 | import sys 11 | 12 | ### 13 | # This script acts as a wrapper around a testing script. 14 | # It is not meant to do any testing itself, instead use the Go python.Import 15 | # option to create a test script that subclasses Daemon and overrides its 16 | # configure and run methods. 17 | ### 18 | 19 | 20 | class Daemon(metaclass=abc.ABCMeta): 21 | """ 22 | The Daemon class is an abstract base class used by all integration tests 23 | against Python libraries. It is meant to be subclassed by individual 24 | packages such as the aioxmpp package 25 | """ 26 | def __init__(self) -> None: 27 | super().__init__() 28 | self.argparse: argparse.ArgumentParser = argparse.ArgumentParser() 29 | self.config: configparser.ConfigParser = configparser.ConfigParser() 30 | 31 | def prepare_argparse(self) -> None: 32 | """ 33 | The prepare_argparse method can be overridden in a subclass to add 34 | custom arguments to the Python script used by tests. 35 | """ 36 | self.argparse.add_argument( 37 | "-c", 38 | default="python_config.ini", 39 | type=argparse.FileType("r"), 40 | help="The config file to load", 41 | ) 42 | 43 | def configure(self): 44 | """ 45 | The configure method parses arguments and reads the config file. 46 | It should be overridden by a subclass to also construct the XMPP client 47 | or other resources needed by the test. 48 | """ 49 | self.args: argparse.Namespace = self.argparse.parse_args() 50 | self.config.read_file(self.args.c) 51 | 52 | async def run_test(self) -> None: 53 | """ 54 | The run_test method should be overridden by the test script or another 55 | testing helper package. 56 | """ 57 | raise NotImplementedError("test file must override run function") 58 | 59 | 60 | async def run_test(cls): 61 | instance = cls() 62 | instance.prepare_argparse() 63 | instance.configure() 64 | await instance.run_test() 65 | 66 | if __name__ == "__main__": 67 | print("running {{ len .Imports }} python scripts…", file=sys.stderr) 68 | {{- range $idx, $script := .Imports }} 69 | def runner(): 70 | from {{ index $script 0 }} import {{ index $script 1 }} 71 | asyncio.run(run_test({{ index $script 1}})) 72 | runner() 73 | {{- end }} 74 | -------------------------------------------------------------------------------- /internal/integration/sendxmpp/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package sendxmpp 6 | 7 | import ( 8 | "text/template" 9 | 10 | "mellium.im/xmpp/jid" 11 | ) 12 | 13 | // Config contains options that can be written to a sendxmpp config file. 14 | type Config struct { 15 | JID jid.JID 16 | Port string 17 | Password string 18 | Component string 19 | } 20 | 21 | const cfgBase = `username: {{ .JID.Localpart }} 22 | jserver: {{ .JID.Domainpart }} 23 | port: {{ .Port }} 24 | password: {{ .Password }} 25 | {{ if .Component}}component: {{ .Component }}{{ end }}` 26 | 27 | var cfgTmpl = template.Must(template.New("cfg").Parse(cfgBase)) 28 | -------------------------------------------------------------------------------- /internal/integration/slixmpp/slixmpp_client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Mellium Contributors. 2 | # Use of this source code is governed by the BSD 2-clause 3 | # license that can be found in the LICENSE file. 4 | 5 | from slixmpp import jid 6 | import slixmpp 7 | import ssl 8 | import xmpptest 9 | 10 | 11 | ### 12 | # This script acts as a wrapper around slixmpp that starts a simple client. 13 | # It is not meant to do any testing itself, instead use the Go python.Import 14 | # option to create a test script that subclasses Daemon and overrides its run 15 | # method. 16 | ### 17 | 18 | class Daemon(xmpptest.Daemon): 19 | def __init__(self) -> None: 20 | super().__init__() 21 | 22 | def configure(self): 23 | super().configure() 24 | self.jid: jid.JID = jid.JID(self.config.get("client", "jid")) 25 | self.xmpp_port = self.config.getint("client", "port") 26 | self.client: slixmpp.ClientXMPP = slixmpp.ClientXMPP( 27 | self.jid, 28 | self.config.get("client", "password"), 29 | ) 30 | 31 | async def run_test(self) -> None: 32 | async def run_callback(_): 33 | await self.run() 34 | self.client.add_event_handler('session_start', run_callback) 35 | # These are integration tests that don't use a real certificate 36 | # so disable verification so that our self-signed certs work. 37 | tlsCtx = self.client.get_ssl_context() 38 | tlsCtx.check_hostname=False 39 | tlsCtx.verify_mode=ssl.CERT_NONE 40 | self.client.connect(address=("127.0.0.1", self.xmpp_port), 41 | force_starttls=False) 42 | await self.client.wait_until('session_end') 43 | 44 | async def run(self) -> None: 45 | """ 46 | The run method should be overridden by the test script and perform any 47 | actions required for testing such as listening for incoming messages and 48 | responding using self.client. 49 | """ 50 | raise NotImplementedError("test file must override run function") 51 | -------------------------------------------------------------------------------- /internal/integration/waitdial.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package integration 6 | 7 | import ( 8 | "fmt" 9 | "net" 10 | "time" 11 | ) 12 | 13 | func waitSocket(network, socket string) error { 14 | timeout := time.Second 15 | for connAttempts := 10; connAttempts > 0; connAttempts-- { 16 | time.Sleep(timeout) 17 | timeout += 500 * time.Millisecond 18 | conn, err := net.DialTimeout(network, socket, timeout) 19 | if err != nil { 20 | continue 21 | } 22 | if err = conn.Close(); err != nil { 23 | return fmt.Errorf("failed to close probe connection: %w", err) 24 | } 25 | return nil 26 | } 27 | return fmt.Errorf("failed to bind to %s", socket) 28 | } 29 | -------------------------------------------------------------------------------- /internal/ns/ns.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package ns provides namespace constants that are used by the xmpp package and 6 | // other internal packages. 7 | package ns // import "mellium.im/xmpp/internal/ns" 8 | 9 | // List of commonly used namespaces. 10 | const ( 11 | Bind = "urn:ietf:params:xml:ns:xmpp-bind" 12 | SASL = "urn:ietf:params:xml:ns:xmpp-sasl" 13 | StartTLS = "urn:ietf:params:xml:ns:xmpp-tls" 14 | XML = "http://www.w3.org/XML/1998/namespace" 15 | ) 16 | -------------------------------------------------------------------------------- /internal/saslerr/condition_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Condition -linecomment"; DO NOT EDIT. 2 | 3 | package saslerr 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[ConditionNone-0] 12 | _ = x[ConditionAborted-1] 13 | _ = x[ConditionAccountDisabled-2] 14 | _ = x[ConditionCredentialsExpired-3] 15 | _ = x[ConditionEncryptionRequired-4] 16 | _ = x[ConditionIncorrectEncoding-5] 17 | _ = x[ConditionInvalidAuthzID-6] 18 | _ = x[ConditionInvalidMechanism-7] 19 | _ = x[ConditionMalformedRequest-8] 20 | _ = x[ConditionMechanismTooWeak-9] 21 | _ = x[ConditionNotAuthorized-10] 22 | _ = x[ConditionTemporaryAuthFailure-11] 23 | } 24 | 25 | const _Condition_name = "noneabortedaccount-disabledcredentials-expiredencryption-requiredincorrect-encodinginvalid-authzidinvalid-mechanismmalformed-requestmechanism-too-weaknot-authorizedtemporary-auth-failure" 26 | 27 | var _Condition_index = [...]uint8{0, 4, 11, 27, 46, 65, 83, 98, 115, 132, 150, 164, 186} 28 | 29 | func (i Condition) String() string { 30 | if i >= Condition(len(_Condition_index)-1) { 31 | return "Condition(" + strconv.FormatInt(int64(i), 10) + ")" 32 | } 33 | return _Condition_name[_Condition_index[i]:_Condition_index[i+1]] 34 | } 35 | -------------------------------------------------------------------------------- /internal/saslerr/condition_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package saslerr 6 | 7 | // ConditionIndex is exported only during tests for the saslerr_test package to 8 | // use. 9 | var ConditionIndex = _Condition_index 10 | -------------------------------------------------------------------------------- /internal/wskey/key.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package wskey is a context key used by negotiators. 6 | // 7 | // We are doing exactly what the context package tells us not to do and using it 8 | // to pass optional arguments to the function (in this case, whether to use the 9 | // WebSocket subprotocol or not). 10 | // A better way to do this would be to move the negotiator to an 11 | // internal/negotiator package and have xmpp and websocket both import and use 12 | // that. 13 | // Unfortunately, that would cause import loops (because the negotiator function 14 | // takes an xmpp.Session, so the internal/negotiator package would also need to 15 | // import the xmpp package). 16 | // We could also copy/pate the entire implementation into websocket, but this is 17 | // a maintainability nightmare. 18 | // 19 | // Having a secret internal API may not be ideal, but it does let us get away 20 | // with a nice surface API without any real drawbacks other than an extra tiny 21 | // internal package to house this key. 22 | package wskey // import "mellium.im/xmpp/internal/wskey" 23 | 24 | // Key is an internal type used as a context key by the xmpp and websocket 25 | // packages. 26 | // If it is provided on a context to xmpp.NewNegotiator, the WebSocket 27 | // subprotocol is used instead of the normal XMPP protocol. 28 | type Key struct{} 29 | -------------------------------------------------------------------------------- /internal/xmpptest/marshal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xmpptest 6 | 7 | import ( 8 | "encoding/xml" 9 | "errors" 10 | "reflect" 11 | "strconv" 12 | "testing" 13 | ) 14 | 15 | // EncodingTestCase is a test that marshals the value and checks that the result 16 | // matches XML, then unmarshals XML into a new zero value of the type in value 17 | // and checks that it matches the original value with reflect.DeepEqual. 18 | // If NoMarshal or NoUnmarshal is set then the corresponding part of the test is 19 | // not run (for payloads that are not roundtrippable). 20 | type EncodingTestCase struct { 21 | Value interface{} 22 | XML string 23 | Err error 24 | NoMarshal bool 25 | NoUnmarshal bool 26 | } 27 | 28 | // RunEncodingTests iterates over the test cases and runs each one. 29 | func RunEncodingTests(t *testing.T, testCases []EncodingTestCase) { 30 | for i, tc := range testCases { 31 | t.Run(strconv.Itoa(i), func(t *testing.T) { 32 | if !tc.NoMarshal { 33 | t.Run("marshal", func(t *testing.T) { 34 | x, err := xml.Marshal(tc.Value) 35 | if !errors.Is(err, tc.Err) { 36 | t.Fatalf("unexpected error: want=%v, got=%v", tc.Err, err) 37 | } 38 | if out := string(x); out != tc.XML { 39 | t.Fatalf("unexpected output:\nwant=%q,\n got=%q", tc.XML, out) 40 | } 41 | }) 42 | } 43 | if !tc.NoUnmarshal { 44 | t.Run("unmarshal", func(t *testing.T) { 45 | valType := reflect.TypeOf(tc.Value).Elem() 46 | newVal := reflect.New(valType).Interface() 47 | err := xml.Unmarshal([]byte(tc.XML), &newVal) 48 | if !errors.Is(err, tc.Err) { 49 | t.Fatalf("unexpected error: want=%v, got=%v", tc.Err, err) 50 | } 51 | if !reflect.DeepEqual(newVal, tc.Value) { 52 | t.Fatalf("unexpected value:\nwant=%+v,\n got=%+v", tc.Value, newVal) 53 | } 54 | }) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /internal/xmpptest/marshal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xmpptest_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "testing" 10 | 11 | "mellium.im/xmpp/internal/xmpptest" 12 | ) 13 | 14 | var marshalTestCases = []xmpptest.EncodingTestCase{ 15 | 0: { 16 | NoMarshal: true, 17 | Value: &struct{ Foo int }{Foo: 0}, 18 | XML: `0`, 19 | }, 20 | 1: { 21 | NoUnmarshal: true, 22 | Value: &struct { 23 | XMLName xml.Name `xml:"foo"` 24 | Foo int `xml:",chardata"` 25 | }{Foo: 0}, 26 | XML: `0`, 27 | }, 28 | } 29 | 30 | func TestEncode(t *testing.T) { 31 | xmpptest.RunEncodingTests(t, marshalTestCases) 32 | } 33 | -------------------------------------------------------------------------------- /internal/xmpptest/session_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xmpptest_test 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "encoding/xml" 11 | "testing" 12 | 13 | "mellium.im/xmlstream" 14 | "mellium.im/xmpp" 15 | "mellium.im/xmpp/internal/xmpptest" 16 | "mellium.im/xmpp/stanza" 17 | ) 18 | 19 | func TestNewSession(t *testing.T) { 20 | state := xmpp.Secure | xmpp.InputStreamClosed 21 | buf := new(bytes.Buffer) 22 | s := xmpptest.NewClientSession(state, buf) 23 | 24 | if mask := s.State(); mask != state|xmpp.Ready { 25 | t.Errorf("Got invalid state value: want=%d, got=%d", state, mask) 26 | } 27 | 28 | if out := buf.String(); out != "" { 29 | t.Errorf("Buffer wrote unexpected tokens: `%s'", out) 30 | } 31 | } 32 | 33 | func TestNewClient(t *testing.T) { 34 | clientState := xmpp.Secure 35 | serverState := xmpp.Secure | xmpp.Authn 36 | s := xmpptest.NewClientServer( 37 | xmpptest.ClientState(clientState), 38 | xmpptest.ServerState(serverState), 39 | xmpptest.ServerHandlerFunc(func(t xmlstream.TokenReadEncoder, start *xml.StartElement) error { 40 | iq, err := stanza.NewIQ(*start) 41 | if err != nil { 42 | panic(err) 43 | } 44 | r := iq.Result(nil) 45 | _, err = xmlstream.Copy(t, r) 46 | return err 47 | })) 48 | if st := s.Client.State(); st&clientState != clientState { 49 | t.Errorf("client state was not added to the session: want %b to include %b", st, clientState) 50 | } 51 | if st := s.Server.State(); st&serverState != serverState { 52 | t.Errorf("server state was not added to the session: want %b to include %b", st, serverState) 53 | } 54 | if st := s.Server.State(); st&xmpp.Received != xmpp.Received { 55 | t.Errorf("expected server state to always include Received") 56 | } 57 | origIQ := struct { 58 | stanza.IQ 59 | }{ 60 | IQ: stanza.IQ{ 61 | ID: "123", 62 | }, 63 | } 64 | resp, err := s.Client.EncodeIQ(context.Background(), origIQ) 65 | if err != nil { 66 | t.Errorf("error encoding IQ: %v", err) 67 | } 68 | iq := stanza.IQ{} 69 | err = xml.NewTokenDecoder(resp).Decode(&iq) 70 | if err != nil { 71 | t.Errorf("error decoding response: %v", err) 72 | } 73 | err = resp.Close() 74 | if err != nil { 75 | t.Errorf("error closing response: %v", err) 76 | } 77 | if iq.ID != origIQ.ID { 78 | t.Errorf("Response IQ had wrong ID: want=%s, got=%s", origIQ.ID, iq.ID) 79 | } 80 | err = s.Close() 81 | if err != nil { 82 | t.Errorf("error closing: %v", err) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /internal/xmpptest/tokens.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xmpptest 6 | 7 | import ( 8 | "encoding/xml" 9 | "io" 10 | ) 11 | 12 | // Tokens is a slice of XML tokens that can also act as an xml.TokenReader by 13 | // popping tokens from itself. 14 | // This is useful for testing contrived scenarios where the tokens cannot be 15 | // constructed using an xml.Decoder because the stream to be tested violates the 16 | // well-formedness rules of XML or otherwise would result in an error from the 17 | // decoder. 18 | type Tokens []xml.Token 19 | 20 | // Token satisfies the xml.TokenReader interface for Tokens. 21 | func (r *Tokens) Token() (xml.Token, error) { 22 | if len(*r) == 0 { 23 | return nil, io.EOF 24 | } 25 | 26 | var t xml.Token 27 | t, *r = (*r)[0], (*r)[1:] 28 | return t, nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/xmpptest/tokens_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xmpptest_test 6 | 7 | import ( 8 | "io" 9 | "testing" 10 | 11 | "mellium.im/xmpp/internal/xmpptest" 12 | ) 13 | 14 | func TestTokens(t *testing.T) { 15 | toks := xmpptest.Tokens{0, 1, 2} 16 | for i := 0; i < 3; i++ { 17 | tok, err := toks.Token() 18 | if err != nil { 19 | t.Errorf("unexpected error on token %d: %v", i, err) 20 | } 21 | if tok.(int) != i { 22 | t.Errorf("unexpcted token: want=%d, got=%v", i, tok) 23 | } 24 | } 25 | 26 | tok, err := toks.Token() 27 | if err != io.EOF { 28 | t.Errorf("unexpected error: want=%v, got=%v", io.EOF, err) 29 | } 30 | if tok != nil { 31 | t.Errorf("unexpcted token: %v", tok) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/xmpptest/transformers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xmpptest 6 | 7 | import ( 8 | "encoding/xml" 9 | "strconv" 10 | "strings" 11 | "testing" 12 | 13 | "mellium.im/xmlstream" 14 | ) 15 | 16 | // TransformerTestCase is a data driven test for XML transformers. 17 | type TransformerTestCase struct { 18 | In string 19 | Out string 20 | 21 | // If InStream is not nil, it will be used instead of "In" and should result 22 | // in tokens matching Out. 23 | InStream xml.TokenReader 24 | } 25 | 26 | func RunTransformerTests(t *testing.T, T xmlstream.Transformer, tcs []TransformerTestCase) { 27 | for i, tc := range tcs { 28 | t.Run(strconv.Itoa(i), func(t *testing.T) { 29 | var d xml.TokenReader 30 | if tc.InStream != nil { 31 | d = T(tc.InStream) 32 | } else { 33 | d = T(xml.NewDecoder(strings.NewReader(tc.In))) 34 | } 35 | buf := &strings.Builder{} 36 | e := xml.NewEncoder(buf) 37 | if _, err := xmlstream.Copy(e, d); err != nil { 38 | t.Fatalf("error copying tokens: %q", err) 39 | } 40 | if err := e.Flush(); err != nil { 41 | t.Fatalf("error flushing tokens: %q", err) 42 | } 43 | if s := buf.String(); s != tc.Out { 44 | t.Errorf("output does not match: want=%q, got=%q", tc.Out, s) 45 | } 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /jid/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature -vars=FeatureEscaping:`jid\20escaping`"; DO NOT EDIT. 2 | 3 | package jid 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | FeatureEscaping = info.Feature{Var: `jid\20escaping`} 12 | ) 13 | -------------------------------------------------------------------------------- /jid/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:generate go run ../internal/genfeature -vars=FeatureEscaping:`jid\20escaping` 6 | 7 | // Package jid implements the XMPP address format. 8 | // 9 | // XMPP addresses, more often called "JID's" (Jabber ID's) for historical 10 | // reasons, comprise three parts: 11 | // The localpart represents a specific user account, the domainpart is the 12 | // domain, host name, or IP address of a server hosting the account, and the 13 | // resourcepart which represents a specific client connected to an account (eg. 14 | // the users phone or a web browser). 15 | // Only the domainpart is required, and together they are formatted like an 16 | // email with the resourcepart appended after a forward slash. 17 | // For example, the following are all valid JIDs: 18 | // 19 | // shakespeare@example.net 20 | // shakespeare@example.net/phone-b5c93ded 21 | // example.net 22 | // 23 | // The first represents the account "shakespeare" on the service "example.net", 24 | // the second represents a specific phone connected to that account, and the 25 | // third represents the server running the service at example.net. 26 | // This means that clients connected to the XMPP network are individually and 27 | // globally addressable. 28 | // 29 | // The jid package also implements the escaping mechanism defined in XEP-0106: 30 | // JID Escaping. 31 | // This can be used to expand the supported characters in the username of a JID. 32 | // 33 | // Be advised: This API is still unstable and is subject to change. 34 | package jid // import "mellium.im/xmpp/jid" 35 | -------------------------------------------------------------------------------- /jid/fuzz_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build gofuzzbeta 6 | // +build gofuzzbeta 7 | 8 | package jid_test 9 | 10 | import ( 11 | "testing" 12 | 13 | "mellium.im/xmpp/jid" 14 | ) 15 | 16 | func FuzzParseJID(f *testing.F) { 17 | f.Add("@") 18 | f.Add("/") 19 | f.Add("xn--") 20 | f.Add("test@example.net") 21 | f.Fuzz(func(t *testing.T, j string) { 22 | parsed, err := jid.Parse(j) 23 | if err != nil { 24 | t.Skip() 25 | } 26 | s := parsed.String() 27 | parsed2, err := jid.Parse(s) 28 | if err != nil { 29 | t.Fatalf("failed to parse a JID that encodes successfully: %q", s) 30 | } 31 | if !parsed.Equal(parsed2) { 32 | t.Errorf("JID parsing/encoding is unstable: %q, %q", parsed, parsed2) 33 | } 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /jid/unsafe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package jid 6 | 7 | // Unsafe is a JID that has not had any normalization, length checks, UTF-8 8 | // validation, or other safety measures applied. 9 | // 10 | // It can be a source of bugs, or even a security risk, if used improperly. 11 | type Unsafe struct { 12 | JID 13 | } 14 | 15 | // NewUnsafe constructs a new unsafe JID. 16 | // For more information, see the Unsafe type. 17 | func NewUnsafe(localpart, domainpart, resourcepart string) Unsafe { 18 | data := make([]byte, 0, len(localpart)+len(domainpart)+len(resourcepart)) 19 | data = append(data, []byte(localpart)...) 20 | data = append(data, []byte(domainpart)...) 21 | data = append(data, []byte(resourcepart)...) 22 | return Unsafe{ 23 | JID: JID{ 24 | locallen: len(localpart), 25 | domainlen: len(domainpart), 26 | data: data, 27 | }, 28 | } 29 | } 30 | 31 | // ParseUnsafe constructs a new unsafe JID from a string. 32 | // For more information, see the Unsafe type. 33 | func ParseUnsafe(s string) (Unsafe, error) { 34 | localpart, domainpart, resourcepart, err := splitString(s, false) 35 | return NewUnsafe(localpart, domainpart, resourcepart), err 36 | } 37 | -------------------------------------------------------------------------------- /jid/unsafe_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package jid_test 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | 11 | "mellium.im/xmpp/jid" 12 | ) 13 | 14 | func TestInvalidUnsafeParseJIDs(t *testing.T) { 15 | for i, tc := range invalidJIDs { 16 | t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 17 | _, err := jid.ParseUnsafe(tc) 18 | if err != nil { 19 | t.Errorf("Expected unsafe JID to be valid, got: %q", err) 20 | } 21 | }) 22 | } 23 | } 24 | 25 | func TestInvalidUnsafeJIDs(t *testing.T) { 26 | for i, tc := range invalidParts { 27 | t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 28 | defer func() { 29 | if r := recover(); r != nil { 30 | t.Errorf("Unexpected panic in NewUnsafe: %q", r) 31 | } 32 | }() 33 | u := jid.NewUnsafe(tc.lp, tc.dp, tc.rp) 34 | if u.Localpart() != tc.lp { 35 | t.Errorf("Unexpected localpart") 36 | } 37 | if u.Domainpart() != tc.dp { 38 | t.Errorf("Unexpected domainpart") 39 | } 40 | if u.Resourcepart() != tc.rp { 41 | t.Errorf("Unexpected resourcepart") 42 | } 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /muc/affiliation_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Affiliation,Role,Privileges -linecomment"; DO NOT EDIT. 2 | 3 | package muc 4 | 5 | import "strconv" 6 | 7 | const _Affiliation_name = "noneowneradminmemberoutcast" 8 | 9 | var _Affiliation_index = [...]uint8{0, 4, 9, 14, 20, 27} 10 | 11 | func (i Affiliation) String() string { 12 | if i >= Affiliation(len(_Affiliation_index)-1) { 13 | return "Affiliation(" + strconv.FormatInt(int64(i), 10) + ")" 14 | } 15 | return _Affiliation_name[_Affiliation_index[i]:_Affiliation_index[i+1]] 16 | } 17 | 18 | const _Role_name = "nonemoderatorparticipantvisitor" 19 | 20 | var _Role_index = [...]uint8{0, 4, 13, 24, 31} 21 | 22 | func (i Role) String() string { 23 | if i >= Role(len(_Role_index)-1) { 24 | return "Role(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | return _Role_name[_Role_index[i]:_Role_index[i+1]] 27 | } 28 | 29 | const _Privileges_name = "presentreceive-messagesreceive-presencebroadcast-presencechange-availabilitychange-nicksend-private-messagesend-invitessend-messagesmodify-subjectkickgrant-voicerevoke-voice" 30 | 31 | var _Privileges_map = map[Privileges]string{ 32 | 1: _Privileges_name[0:7], 33 | 2: _Privileges_name[7:23], 34 | 4: _Privileges_name[23:39], 35 | 8: _Privileges_name[39:57], 36 | 16: _Privileges_name[57:76], 37 | 32: _Privileges_name[76:87], 38 | 64: _Privileges_name[87:107], 39 | 128: _Privileges_name[107:119], 40 | 256: _Privileges_name[119:132], 41 | 512: _Privileges_name[132:146], 42 | 1024: _Privileges_name[146:150], 43 | 2048: _Privileges_name[150:161], 44 | 4096: _Privileges_name[161:173], 45 | } 46 | 47 | func (i Privileges) String() string { 48 | if str, ok := _Privileges_map[i]; ok { 49 | return str 50 | } 51 | return "Privileges(" + strconv.FormatInt(int64(i), 10) + ")" 52 | } 53 | -------------------------------------------------------------------------------- /muc/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature -receiver *Client"; DO NOT EDIT. 2 | 3 | package muc 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | 14 | // ForFeatures implements info.FeatureIter. 15 | func (*Client) ForFeatures(node string, f func(info.Feature) error) error { 16 | if node != "" { 17 | return nil 18 | } 19 | var err error 20 | err = f(Feature) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /muc/types_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package muc_test 6 | 7 | import ( 8 | "encoding/xml" 9 | 10 | "mellium.im/xmpp/muc" 11 | ) 12 | 13 | var ( 14 | _ xml.MarshalerAttr = (*muc.Role)(nil) 15 | _ xml.UnmarshalerAttr = (*muc.Role)(nil) 16 | _ xml.MarshalerAttr = (*muc.Affiliation)(nil) 17 | _ xml.UnmarshalerAttr = (*muc.Affiliation)(nil) 18 | ) 19 | -------------------------------------------------------------------------------- /mux/stanza.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package mux 6 | 7 | import ( 8 | "encoding/xml" 9 | 10 | "mellium.im/xmlstream" 11 | "mellium.im/xmpp/stanza" 12 | ) 13 | 14 | // IQHandler responds to IQ stanzas. 15 | type IQHandler interface { 16 | HandleIQ(stanza.IQ, xmlstream.TokenReadEncoder, *xml.StartElement) error 17 | } 18 | 19 | // The IQHandlerFunc type is an adapter to allow the use of ordinary functions 20 | // as IQ handlers. 21 | // If f is a function with the appropriate signature, IQHandlerFunc(f) is an 22 | // IQHandler that calls f. 23 | type IQHandlerFunc func(stanza.IQ, xmlstream.TokenReadEncoder, *xml.StartElement) error 24 | 25 | // HandleIQ calls f(iq, t, start). 26 | func (f IQHandlerFunc) HandleIQ(iq stanza.IQ, t xmlstream.TokenReadEncoder, start *xml.StartElement) error { 27 | return f(iq, t, start) 28 | } 29 | 30 | // MessageHandler responds to message stanzas. 31 | type MessageHandler interface { 32 | HandleMessage(stanza.Message, xmlstream.TokenReadEncoder) error 33 | } 34 | 35 | // The MessageHandlerFunc type is an adapter to allow the use of ordinary 36 | // functions as message handlers. 37 | // If f is a function with the appropriate signature, MessageHandlerFunc(f) is a 38 | // MessageHandler that calls f. 39 | type MessageHandlerFunc func(stanza.Message, xmlstream.TokenReadEncoder) error 40 | 41 | // HandleMessage calls f(msg, t). 42 | func (f MessageHandlerFunc) HandleMessage(msg stanza.Message, t xmlstream.TokenReadEncoder) error { 43 | return f(msg, t) 44 | } 45 | 46 | // PresenceHandler responds to message stanzas. 47 | type PresenceHandler interface { 48 | HandlePresence(stanza.Presence, xmlstream.TokenReadEncoder) error 49 | } 50 | 51 | // The PresenceHandlerFunc type is an adapter to allow the use of ordinary 52 | // functions as presence handlers. 53 | // If f is a function with the appropriate signature, PresenceHandlerFunc(f) is 54 | // a PresenceHandler that calls f. 55 | type PresenceHandlerFunc func(stanza.Presence, xmlstream.TokenReadEncoder) error 56 | 57 | // HandlePresence calls f(p, t). 58 | func (f PresenceHandlerFunc) HandlePresence(p stanza.Presence, t xmlstream.TokenReadEncoder) error { 59 | return f(p, t) 60 | } 61 | -------------------------------------------------------------------------------- /oob/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature -vars=Feature:NS,FeatureIQ:NS"; DO NOT EDIT. 2 | 3 | package oob 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | FeatureIQ = info.Feature{Var: NS} 13 | ) 14 | -------------------------------------------------------------------------------- /paging/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature"; DO NOT EDIT. 2 | 3 | package paging 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | -------------------------------------------------------------------------------- /ping/aioxmpp_integration_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Mellium Contributors. 2 | # Use of this source code is governed by the BSD 2-clause 3 | # license that can be found in the LICENSE file. 4 | 5 | from aioxmpp_client import Daemon 6 | import aioxmpp 7 | 8 | 9 | class Ping(Daemon): 10 | def prepare_argparse(self) -> None: 11 | super().prepare_argparse() 12 | 13 | def jid(s) -> aioxmpp.JID: 14 | return aioxmpp.JID.fromstr(s) 15 | self.argparse.add_argument( 16 | "-j", 17 | type=jid, 18 | help="The JID to ping", 19 | ) 20 | 21 | async def run(self) -> None: 22 | await aioxmpp.ping.ping(self.client, self.args.j) 23 | -------------------------------------------------------------------------------- /ping/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature -receiver h Handler"; DO NOT EDIT. 2 | 3 | package ping 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | 14 | // ForFeatures implements info.FeatureIter. 15 | func (h Handler) ForFeatures(node string, f func(info.Feature) error) error { 16 | if node != "" { 17 | return nil 18 | } 19 | var err error 20 | err = f(Feature) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /ping/slixmpp_integration_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Mellium Contributors. 2 | # Use of this source code is governed by the BSD 2-clause 3 | # license that can be found in the LICENSE file. 4 | 5 | from slixmpp_client import Daemon 6 | import slixmpp 7 | 8 | 9 | class Ping(Daemon): 10 | def prepare_argparse(self) -> None: 11 | super().prepare_argparse() 12 | 13 | self.argparse.add_argument( 14 | "-j", 15 | type=slixmpp.jid.JID, 16 | help="The JID to ping", 17 | ) 18 | 19 | def configure(self): 20 | super().configure() 21 | self.client.register_plugin('xep_0199') # Ping 22 | 23 | async def run(self) -> None: 24 | await self.client.plugin['xep_0199'].ping(jid=self.args.j) 25 | -------------------------------------------------------------------------------- /pubsub/create.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package pubsub 6 | 7 | import ( 8 | "context" 9 | "encoding/xml" 10 | 11 | "mellium.im/xmlstream" 12 | "mellium.im/xmpp" 13 | "mellium.im/xmpp/form" 14 | "mellium.im/xmpp/stanza" 15 | ) 16 | 17 | // CreateNode adds a new node on the pubsub service with the provided 18 | // configuration (or the default configuration if none is provided). 19 | func CreateNode(ctx context.Context, s *xmpp.Session, node string, cfg *form.Data) error { 20 | return CreateNodeIQ(ctx, s, stanza.IQ{}, node, cfg) 21 | } 22 | 23 | // CreateNodeIQ is like Publish except that it allows modifying the IQ. 24 | // Changes to the IQ type will have no effect. 25 | func CreateNodeIQ(ctx context.Context, s *xmpp.Session, iq stanza.IQ, node string, cfg *form.Data) error { 26 | iq.Type = stanza.SetIQ 27 | payload := xmlstream.Wrap( 28 | nil, 29 | xml.StartElement{Name: xml.Name{Local: "create"}, Attr: []xml.Attr{{Name: xml.Name{Local: "node"}, Value: node}}}, 30 | ) 31 | if cfg != nil { 32 | submitted, _ := cfg.Submit() 33 | payload = xmlstream.MultiReader(payload, xmlstream.Wrap( 34 | submitted, 35 | xml.StartElement{Name: xml.Name{Local: "configure"}}, 36 | )) 37 | } 38 | 39 | return s.UnmarshalIQElement(ctx, xmlstream.Wrap( 40 | payload, 41 | xml.StartElement{Name: xml.Name{Space: NS, Local: "pubsub"}}, 42 | ), iq, nil) 43 | } 44 | -------------------------------------------------------------------------------- /pubsub/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:generate go run ../internal/genpubsub 6 | //go:generate go run -tags=tools golang.org/x/tools/cmd/stringer -output=string.go -type=SubType,Condition,Feature -linecomment 7 | 8 | // Package pubsub implements data storage using a publish–subscribe pattern. 9 | package pubsub // import "mellium.im/xmpp/pubsub" 10 | 11 | // Various namespaces used by this package, provided as a convenience. 12 | const ( 13 | NS = `http://jabber.org/protocol/pubsub` 14 | NSErrors = `http://jabber.org/protocol/pubsub#errors` 15 | NSEvent = `http://jabber.org/protocol/pubsub#event` 16 | NSOptions = `http://jabber.org/protocol/pubsub#subscription-options` 17 | NSOwner = `http://jabber.org/protocol/pubsub#owner` 18 | NSPaging = `http://jabber.org/protocol/pubsub#rsm` 19 | ) 20 | -------------------------------------------------------------------------------- /pubsub/pubsub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package pubsub 6 | 7 | import ( 8 | "context" 9 | "encoding/xml" 10 | 11 | "mellium.im/xmlstream" 12 | "mellium.im/xmpp" 13 | "mellium.im/xmpp/stanza" 14 | ) 15 | 16 | type publishResponse struct { 17 | XMLName xml.Name `xml:"http://jabber.org/protocol/pubsub pubsub"` 18 | Publish struct { 19 | Item struct { 20 | ID string `xml:"id,attr"` 21 | } `xml:"item"` 22 | } `xml:"publish"` 23 | } 24 | 25 | // Publish copies the first element from the provided token reader to a node on 26 | // the server from which it can be retrieved later. 27 | func Publish(ctx context.Context, s *xmpp.Session, node, id string, item xml.TokenReader) (string, error) { 28 | return PublishIQ(ctx, s, stanza.IQ{}, node, id, item) 29 | } 30 | 31 | // PublishIQ is like Publish except that it allows modifying the IQ. 32 | // Changes to the IQ type will have no effect. 33 | func PublishIQ(ctx context.Context, s *xmpp.Session, iq stanza.IQ, node, id string, item xml.TokenReader) (string, error) { 34 | iq.Type = stanza.SetIQ 35 | start, err := item.Token() 36 | if err != nil { 37 | return "", err 38 | } 39 | itemAttrs := []xml.Attr{} 40 | if id != "" { 41 | itemAttrs = append(itemAttrs, xml.Attr{ 42 | Name: xml.Name{Local: "id"}, 43 | Value: id, 44 | }) 45 | } 46 | resp := publishResponse{} 47 | err = s.UnmarshalIQElement(ctx, xmlstream.Wrap( 48 | xmlstream.Wrap( 49 | xmlstream.Wrap( 50 | xmlstream.MultiReader(xmlstream.Token(start), xmlstream.InnerElement(item)), 51 | xml.StartElement{Name: xml.Name{Local: "item"}, Attr: itemAttrs}, 52 | ), 53 | xml.StartElement{Name: xml.Name{Local: "publish"}, Attr: []xml.Attr{{Name: xml.Name{Local: "node"}, Value: node}}}, 54 | ), 55 | xml.StartElement{Name: xml.Name{Space: NS, Local: "pubsub"}}, 56 | ), iq, &resp) 57 | if resp.Publish.Item.ID == "" { 58 | return id, err 59 | } 60 | return resp.Publish.Item.ID, err 61 | } 62 | -------------------------------------------------------------------------------- /pubsub/retract.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package pubsub 6 | 7 | import ( 8 | "context" 9 | "encoding/xml" 10 | 11 | "mellium.im/xmlstream" 12 | "mellium.im/xmpp" 13 | "mellium.im/xmpp/stanza" 14 | ) 15 | 16 | // Delete removes an item from the pubsub node. 17 | func Delete(ctx context.Context, s *xmpp.Session, node, id string, notify bool) error { 18 | return DeleteIQ(ctx, s, stanza.IQ{}, node, id, notify) 19 | } 20 | 21 | // DeleteIQ is like Publish except that it allows modifying the IQ. 22 | // Changes to the IQ type will have no effect. 23 | func DeleteIQ(ctx context.Context, s *xmpp.Session, iq stanza.IQ, node, id string, notify bool) error { 24 | iq.Type = stanza.SetIQ 25 | retractAttrs := []xml.Attr{{Name: xml.Name{Local: "node"}, Value: node}} 26 | if notify { 27 | retractAttrs = append(retractAttrs, xml.Attr{ 28 | Name: xml.Name{Local: "notify"}, 29 | Value: "true", 30 | }) 31 | } 32 | return s.UnmarshalIQElement(ctx, xmlstream.Wrap( 33 | xmlstream.Wrap( 34 | xmlstream.Wrap( 35 | nil, 36 | xml.StartElement{Name: xml.Name{Local: "item"}, Attr: []xml.Attr{{Name: xml.Name{Local: "id"}, Value: id}}}, 37 | ), 38 | xml.StartElement{Name: xml.Name{Local: "retract"}, Attr: retractAttrs}, 39 | ), 40 | xml.StartElement{Name: xml.Name{Space: NS, Local: "pubsub"}}, 41 | ), iq, nil) 42 | } 43 | -------------------------------------------------------------------------------- /receipts/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature -receiver h *Handler"; DO NOT EDIT. 2 | 3 | package receipts 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | 14 | // ForFeatures implements info.FeatureIter. 15 | func (h *Handler) ForFeatures(node string, f func(info.Feature) error) error { 16 | if node != "" { 17 | return nil 18 | } 19 | var err error 20 | err = f(Feature) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /roster/ver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package roster 6 | 7 | import ( 8 | "context" 9 | "encoding/xml" 10 | "io" 11 | 12 | "mellium.im/xmlstream" 13 | "mellium.im/xmpp" 14 | ) 15 | 16 | // Versioning returns a stream feature that advertises roster versioning 17 | // support. 18 | // 19 | // Actually attempting to negotiate the feature does nothing as it is meant to 20 | // be informational only. 21 | func Versioning() xmpp.StreamFeature { 22 | return xmpp.StreamFeature{ 23 | Name: xml.Name{Space: NSFeatures, Local: "ver"}, 24 | Necessary: xmpp.Secure, 25 | List: func(_ context.Context, e xmlstream.TokenWriter, start xml.StartElement) (bool, error) { 26 | err := e.EncodeToken(start) 27 | if err != nil { 28 | return true, err 29 | } 30 | return true, e.EncodeToken(start.End()) 31 | }, 32 | Parse: func(_ context.Context, d *xml.Decoder, _ *xml.StartElement) (bool, interface{}, error) { 33 | return false, nil, d.Skip() 34 | }, 35 | Negotiate: func(context.Context, *xmpp.Session, interface{}) (xmpp.SessionState, io.ReadWriter, error) { 36 | return 0, nil, nil 37 | }, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /s2s/bidi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package s2s 6 | 7 | import ( 8 | "context" 9 | "encoding/xml" 10 | "io" 11 | 12 | "mellium.im/xmlstream" 13 | "mellium.im/xmpp" 14 | ) 15 | 16 | // Namespaces used in this package, provided as a convenience. 17 | const ( 18 | // NSBidi is the namespace used for negotiating bidirectional S2S connections. 19 | NSBidi = "urn:xmpp:bidi" 20 | 21 | // NSBidiFeature is the namespace used for advertising Bidi support. 22 | NSBidiFeature = "urn:xmpp:features:bidi" 23 | ) 24 | 25 | // Bidi returns a stream feature for indicating support for bidirectional 26 | // server-to-server connections (ie. s2s connections over a single bidirectional 27 | // TCP connection instead of over two unidirectional TCP connections). 28 | // 29 | // The feature itself is just informational, servers using this feature will 30 | // need to check if it was negotiated and handle their connections 31 | // appropriately. 32 | func Bidi() xmpp.StreamFeature { 33 | return xmpp.StreamFeature{ 34 | Name: xml.Name{Space: NSBidiFeature, Local: "bidi"}, 35 | Necessary: xmpp.Secure, 36 | Prohibited: xmpp.Authn, 37 | List: func(ctx context.Context, e xmlstream.TokenWriter, start xml.StartElement) (bool, error) { 38 | if err := e.EncodeToken(start); err != nil { 39 | return false, err 40 | } 41 | return false, e.EncodeToken(start.End()) 42 | }, 43 | Parse: func(ctx context.Context, d *xml.Decoder, start *xml.StartElement) (bool, interface{}, error) { 44 | parsed := struct { 45 | XMLName xml.Name `xml:"urn:xmpp:features:bidi bidi"` 46 | }{} 47 | return false, nil, d.DecodeElement(&parsed, start) 48 | }, 49 | Negotiate: func(ctx context.Context, session *xmpp.Session, data interface{}) (xmpp.SessionState, io.ReadWriter, error) { 50 | if (session.State() & xmpp.Received) == xmpp.Received { 51 | // The BIDI feature is just informational at this point, no need to 52 | // respond if we're a server. 53 | return 0, nil, nil 54 | } 55 | 56 | w := session.TokenWriter() 57 | defer w.Close() 58 | 59 | start := xml.StartElement{ 60 | Name: xml.Name{Space: NSBidi, Local: "bidi"}, 61 | } 62 | err := w.EncodeToken(start) 63 | if err != nil { 64 | return 0, nil, err 65 | } 66 | return 0, nil, w.EncodeToken(start.End()) 67 | }, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /s2s/bidi_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package s2s_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "mellium.im/xmpp" 11 | "mellium.im/xmpp/internal/xmpptest" 12 | "mellium.im/xmpp/s2s" 13 | ) 14 | 15 | var bidiTestCases = [...]xmpptest.FeatureTestCase{ 16 | 0: { 17 | State: xmpp.Received, 18 | Feature: s2s.Bidi(), 19 | In: ``, 20 | }, 21 | 1: { 22 | Feature: s2s.Bidi(), 23 | Out: ``, 24 | }, 25 | } 26 | 27 | func TestBidi(t *testing.T) { 28 | xmpptest.RunFeatureTests(t, bidiTestCases[:]) 29 | } 30 | -------------------------------------------------------------------------------- /s2s/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package s2s implements server-to-server functionality. 6 | package s2s // import "mellium.im/xmpp/s2s" 7 | -------------------------------------------------------------------------------- /sasl_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build integration 6 | // +build integration 7 | 8 | package xmpp_test 9 | 10 | import ( 11 | "context" 12 | "crypto/tls" 13 | "testing" 14 | 15 | "mellium.im/sasl" 16 | "mellium.im/xmpp" 17 | "mellium.im/xmpp/internal/integration" 18 | "mellium.im/xmpp/internal/integration/mcabber" 19 | "mellium.im/xmpp/internal/integration/mellium" 20 | "mellium.im/xmpp/internal/integration/prosody" 21 | "mellium.im/xmpp/jid" 22 | ) 23 | 24 | func TestMain(m *testing.M) { 25 | mellium.TestMain(m) 26 | } 27 | 28 | func TestIntegrationSASLServer(t *testing.T) { 29 | const pass = "testpass" 30 | melliumRun := mellium.Test(context.TODO(), t, 31 | integration.Cert("localhost"), 32 | mellium.ConfigFile(mellium.Config{ 33 | ListenC2S: true, 34 | }), 35 | integration.User(jid.MustParse("me@localhost"), pass), 36 | ) 37 | melliumRun(integrationSASLServer) 38 | } 39 | 40 | func integrationSASLServer(ctx context.Context, t *testing.T, cmd *integration.Cmd) { 41 | j, pass := cmd.User() 42 | p := cmd.C2SPort() 43 | mcabberRun := mcabber.Test(context.TODO(), t, 44 | mcabber.ConfigFile(mcabber.Config{ 45 | JID: j, 46 | Password: pass, 47 | Port: p, 48 | }), 49 | ) 50 | mcabberRun(func(ctx context.Context, t *testing.T, cmd *integration.Cmd) { 51 | t.Log("Connected successfully!") 52 | }) 53 | } 54 | 55 | func TestIntegrationSASLClient(t *testing.T) { 56 | // TODO (#441): unskip once we have a new release of Prosody. 57 | t.Skip("Needs prosody nightly or a new release.") 58 | prosodyRun := prosody.Test(context.TODO(), t, 59 | integration.Log(), 60 | integration.LogXML(), 61 | prosody.ListenC2S(), 62 | ) 63 | prosodyRun(integrationSASLClient) 64 | } 65 | 66 | func integrationSASLClient(ctx context.Context, t *testing.T, cmd *integration.Cmd) { 67 | j, pass := cmd.User() 68 | _, err := cmd.DialClient(ctx, j, t, 69 | xmpp.StartTLS(&tls.Config{ 70 | MinVersion: tls.VersionTLS13, 71 | InsecureSkipVerify: true, 72 | }), 73 | xmpp.SASL("", pass, sasl.ScramSha1Plus), 74 | xmpp.BindResource(), 75 | ) 76 | if err != nil { 77 | t.Fatalf("error connecting: %v", err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /sessionstate_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=SessionState"; DO NOT EDIT. 2 | 3 | package xmpp 4 | 5 | import "strconv" 6 | 7 | const ( 8 | _SessionState_name_0 = "SecureAuthn" 9 | _SessionState_name_1 = "Ready" 10 | _SessionState_name_2 = "Received" 11 | _SessionState_name_3 = "OutputStreamClosed" 12 | _SessionState_name_4 = "InputStreamClosed" 13 | _SessionState_name_5 = "S2S" 14 | ) 15 | 16 | var ( 17 | _SessionState_index_0 = [...]uint8{0, 6, 11} 18 | ) 19 | 20 | func (i SessionState) String() string { 21 | switch { 22 | case 1 <= i && i <= 2: 23 | i -= 1 24 | return _SessionState_name_0[_SessionState_index_0[i]:_SessionState_index_0[i+1]] 25 | case i == 4: 26 | return _SessionState_name_1 27 | case i == 8: 28 | return _SessionState_name_2 29 | case i == 16: 30 | return _SessionState_name_3 31 | case i == 32: 32 | return _SessionState_name_4 33 | case i == 64: 34 | return _SessionState_name_5 35 | default: 36 | return "SessionState(" + strconv.FormatInt(int64(i), 10) + ")" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /stanza/delay.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package stanza 6 | 7 | import ( 8 | "encoding/xml" 9 | "time" 10 | 11 | "mellium.im/xmlstream" 12 | "mellium.im/xmpp/jid" 13 | ) 14 | 15 | // Delay can be added to a stanza to indicate that stanza delivery was delayed. 16 | // For example, when you joing a chat and request history, a delay might be 17 | // added to indicate that the chat messages were sent in the past and are not 18 | // live. 19 | type Delay struct { 20 | From jid.JID 21 | Stamp time.Time 22 | Reason string 23 | } 24 | 25 | // TokenReader satisfies the xmlstream.Marshaler interface. 26 | func (d Delay) TokenReader() xml.TokenReader { 27 | return xmlstream.Wrap(xmlstream.Token(xml.CharData(d.Reason)), xml.StartElement{ 28 | Name: xml.Name{Space: NSDelay, Local: "delay"}, 29 | Attr: []xml.Attr{ 30 | {Name: xml.Name{Local: "from"}, Value: d.From.String()}, 31 | {Name: xml.Name{Local: "stamp"}, Value: d.Stamp.UTC().Format(time.RFC3339Nano)}, 32 | }, 33 | }) 34 | } 35 | 36 | // WriteXML satisfies the xmlstream.WriterTo interface. 37 | // It is like MarshalXML except it writes tokens to w. 38 | func (d Delay) WriteXML(w xmlstream.TokenWriter) (n int, err error) { 39 | return xmlstream.Copy(w, d.TokenReader()) 40 | } 41 | 42 | // MarshalXML implements xml.Marshaler. 43 | func (d Delay) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { 44 | _, err := d.WriteXML(e) 45 | return err 46 | } 47 | 48 | // UnmarshalXML implements xml.Unmarshaler. 49 | func (d *Delay) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) error { 50 | var foundFrom, foundStamp bool 51 | var err error 52 | for _, attr := range start.Attr { 53 | switch attr.Name.Local { 54 | case "from": 55 | d.From, err = jid.Parse(attr.Value) 56 | if err != nil { 57 | return err 58 | } 59 | foundFrom = true 60 | case "stamp": 61 | d.Stamp, err = time.Parse(time.RFC3339Nano, attr.Value) 62 | if err != nil { 63 | return err 64 | } 65 | foundStamp = true 66 | } 67 | if foundFrom && foundStamp { 68 | break 69 | } 70 | } 71 | tok, err := dec.Token() 72 | if err != nil { 73 | return err 74 | } 75 | switch t := tok.(type) { 76 | case xml.EndElement: 77 | return nil 78 | case xml.CharData: 79 | d.Reason = string(t) 80 | case xml.StartElement: 81 | // There shouldn't be a start element in here, but tolerate unknown future 82 | // extensions and skip it if we find one. 83 | err = dec.Skip() 84 | if err != nil { 85 | return err 86 | } 87 | default: 88 | } 89 | return dec.Skip() 90 | } 91 | -------------------------------------------------------------------------------- /stanza/delay_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package stanza_test 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | 11 | "mellium.im/xmpp/internal/xmpptest" 12 | "mellium.im/xmpp/jid" 13 | "mellium.im/xmpp/stanza" 14 | ) 15 | 16 | var marshalDelayTestCases = []xmpptest.EncodingTestCase{ 17 | 0: { 18 | Value: &stanza.Delay{}, 19 | XML: ``, 20 | NoUnmarshal: true, 21 | }, 22 | 1: { 23 | Value: &stanza.Delay{ 24 | From: jid.MustParse("example.net"), 25 | }, 26 | XML: ``, 27 | }, 28 | 2: { 29 | Value: &stanza.Delay{ 30 | From: jid.MustParse("me@example.net"), 31 | Stamp: time.Unix(10000, 0).UTC(), 32 | }, 33 | XML: ``, 34 | }, 35 | 3: { 36 | Value: &stanza.Delay{ 37 | From: jid.MustParse("me@example.net"), 38 | Stamp: time.Unix(10000, 0), 39 | }, 40 | XML: ``, 41 | NoUnmarshal: true, 42 | }, 43 | 4: { 44 | Value: &stanza.Delay{ 45 | From: jid.MustParse("me@example.net"), 46 | Stamp: time.Unix(10000, 0).UTC(), 47 | }, 48 | XML: ``, 49 | NoMarshal: true, 50 | }, 51 | 5: { 52 | Value: &stanza.Delay{ 53 | From: jid.MustParse("me@example.net"), 54 | Stamp: time.Unix(10000, 0).UTC(), 55 | Reason: "test", 56 | }, 57 | XML: `test`, 58 | }, 59 | } 60 | 61 | func TestMarshalDelay(t *testing.T) { 62 | xmpptest.RunEncodingTests(t, marshalDelayTestCases) 63 | } 64 | -------------------------------------------------------------------------------- /stanza/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package stanza contains functionality for dealing with XMPP stanzas and 6 | // stanza level errors. 7 | // 8 | // Stanzas (Message, Presence, and IQ) are the basic building blocks of an XMPP 9 | // stream. 10 | // Messages are used to send data that is fire-and-forget such as chat messages. 11 | // Presence is a publish-subscribe mechanism and is used to broadcast 12 | // availability on the network (sometimes called "status" in chat, eg. online, 13 | // offline, or away). 14 | // IQ (Info/Query) is a request response mechanism for data that requires a 15 | // response (eg. fetching an avatar or a list of client features). 16 | // 17 | // There are two APIs for creating stanzas in this package, a token based XML 18 | // stream API where the final stanza can be read from an xml.TokenReader, and a 19 | // struct based API that relies on embedding structs in this package into the 20 | // users own types. 21 | // Stanzas created using either API are not guaranteed to be valid or enforce 22 | // specific stanza semantics. 23 | // 24 | // # Custom Stanzas 25 | // 26 | // The stanza types in this package aren't very useful by themselves. To 27 | // transmit meaningful data our stanzas must contain a payload. 28 | // To add a payload with the struct based API we use composition to create a new 29 | // struct where the payload is represented by additional fields. 30 | // For example, XEP-0199: XMPP Ping defines an IQ stanza with a payload named 31 | // "ping" qualified by the "urn:xmpp:ping" namespace. 32 | // To implement this in our own code we might create a Ping struct similar to 33 | // the following: 34 | // 35 | // // PingIQ is an IQ stanza with an XEP-0199: XMPP Ping payload. 36 | // type PingIQ struct { 37 | // stanza.IQ 38 | // 39 | // Ping struct{} `xml:"urn:xmpp:ping ping"` 40 | // } 41 | // 42 | // For details on marshaling and the use of the xml tag, refer to the 43 | // encoding/xml package. 44 | // 45 | // We could also create a similar stanza with the token stream API: 46 | // 47 | // // PingIQ returns an xml.TokenReader that outputs a new IQ stanza with a 48 | // // ping payload. 49 | // func PingIQ(id string, to jid.JID) xml.TokenReader { 50 | // start := xml.StartElement{Name: xml.Name{Space: "urn:xmpp:ping", Local: "ping"}} 51 | // return stanza.IQ{ 52 | // ID: id, 53 | // To: to, 54 | // Type: stanza.GetIQ, 55 | // }.Wrap(start) 56 | // } 57 | package stanza // import "mellium.im/xmpp/stanza" 58 | -------------------------------------------------------------------------------- /stanza/example_error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package stanza_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "fmt" 10 | "os" 11 | "strings" 12 | 13 | "mellium.im/xmpp/jid" 14 | "mellium.im/xmpp/stanza" 15 | ) 16 | 17 | func ExampleError_MarshalXML() { 18 | e := xml.NewEncoder(os.Stdout) 19 | e.Indent("", "\t") 20 | 21 | err := e.Encode(stanza.Error{ 22 | By: jid.MustParse("me@example.com"), 23 | Type: stanza.Cancel, 24 | Condition: stanza.BadRequest, 25 | Text: map[string]string{ 26 | "en": "Malformed XML in request", 27 | }, 28 | }) 29 | if err != nil { 30 | panic(err) 31 | } 32 | // Output: 33 | // 34 | // 35 | // Malformed XML in request 36 | // 37 | } 38 | 39 | func ExampleError_UnmarshalXML() { 40 | d := xml.NewDecoder(strings.NewReader(` 41 | 42 | 43 | Malformed XML 44 | `)) 45 | 46 | se := stanza.Error{} 47 | err := d.Decode(&se) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | fmt.Printf("%s: %s", se.Condition, se.Text[""]) 53 | // Output: 54 | // bad-request: Malformed XML 55 | } 56 | -------------------------------------------------------------------------------- /stanza/example_pingstream_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package stanza_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "log" 10 | "os" 11 | 12 | "mellium.im/xmlstream" 13 | "mellium.im/xmpp/jid" 14 | "mellium.im/xmpp/stanza" 15 | ) 16 | 17 | // WrapPingIQ returns an xml.TokenReader that outputs a new IQ stanza with 18 | // a ping payload. 19 | func WrapPingIQ(to jid.JID) xml.TokenReader { 20 | start := xml.StartElement{Name: xml.Name{Local: "ping", Space: "urn:xmpp:ping"}} 21 | return stanza.IQ{To: to, Type: stanza.GetIQ}.Wrap(xmlstream.Wrap(nil, start)) 22 | } 23 | 24 | func Example_stream() { 25 | j := jid.MustParse("feste@example.net/siJo4eeT") 26 | e := xml.NewEncoder(os.Stdout) 27 | e.Indent("", "\t") 28 | 29 | ping := WrapPingIQ(j) 30 | if _, err := xmlstream.Copy(e, ping); err != nil { 31 | log.Fatal(err) 32 | } 33 | if err := e.Flush(); err != nil { 34 | log.Fatal(err) 35 | } 36 | // Output: 37 | // 38 | // 39 | // 40 | } 41 | -------------------------------------------------------------------------------- /stanza/example_pingstruct_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package stanza_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "log" 10 | "os" 11 | 12 | "mellium.im/xmpp/jid" 13 | "mellium.im/xmpp/stanza" 14 | ) 15 | 16 | // PingIQ represents a ping from XEP-0199: XMPP Ping. 17 | type PingIQ struct { 18 | stanza.IQ 19 | 20 | Ping struct{} `xml:"urn:xmpp:ping ping"` 21 | } 22 | 23 | func Example_struct() { 24 | e := xml.NewEncoder(os.Stdout) 25 | e.Indent("", "\t") 26 | 27 | j := jid.MustParse("feste@example.net/siJo4eeT") 28 | err := e.Encode(PingIQ{ 29 | IQ: stanza.IQ{ 30 | Type: stanza.GetIQ, 31 | To: j, 32 | }, 33 | }) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | // Output: 38 | // 39 | // 40 | // 41 | } 42 | -------------------------------------------------------------------------------- /stanza/stanza_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package stanza_test 6 | 7 | import ( 8 | "bytes" 9 | "encoding/xml" 10 | "fmt" 11 | "io" 12 | "strings" 13 | "testing" 14 | 15 | "mellium.im/xmlstream" 16 | "mellium.im/xmpp/jid" 17 | "mellium.im/xmpp/stanza" 18 | ) 19 | 20 | type testReader []xml.Token 21 | 22 | func (r *testReader) Token() (t xml.Token, err error) { 23 | tr := *r 24 | if len(tr) < 1 { 25 | return nil, io.EOF 26 | } 27 | t, *r = tr[0], tr[1:] 28 | return t, nil 29 | } 30 | 31 | var start = xml.StartElement{ 32 | Name: xml.Name{Local: "ping"}, 33 | } 34 | 35 | type messageTest struct { 36 | to string 37 | typ stanza.MessageType 38 | payload xml.TokenReader 39 | out string 40 | err error 41 | } 42 | 43 | var messageTests = [...]messageTest{ 44 | 0: { 45 | to: "new@example.net", 46 | payload: &testReader{}, 47 | }, 48 | 1: { 49 | to: "new@example.org", 50 | payload: &testReader{start, start.End()}, 51 | out: ``, 52 | typ: stanza.NormalMessage, 53 | }, 54 | } 55 | 56 | func TestMessage(t *testing.T) { 57 | for i, tc := range messageTests { 58 | t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 59 | b := new(bytes.Buffer) 60 | e := xml.NewEncoder(b) 61 | message := stanza.Message{To: jid.MustParse(tc.to), Type: tc.typ}.Wrap(tc.payload) 62 | if _, err := xmlstream.Copy(e, message); err != tc.err { 63 | t.Errorf("Unexpected error: want=`%v', got=`%v'", tc.err, err) 64 | } 65 | if err := e.Flush(); err != nil { 66 | t.Fatalf("Error flushing: %q", err) 67 | } 68 | 69 | o := b.String() 70 | jidattr := fmt.Sprintf(`to="%s"`, tc.to) 71 | if !strings.Contains(o, jidattr) { 72 | t.Errorf("Expected output to have attr `%s',\ngot=`%s'", jidattr, o) 73 | } 74 | typeattr := fmt.Sprintf(`type="%s"`, string(tc.typ)) 75 | if !strings.Contains(o, typeattr) { 76 | t.Errorf("Expected output to have attr `%s',\ngot=`%s'", typeattr, o) 77 | } 78 | if !strings.Contains(o, tc.out) { 79 | t.Errorf("Expected output to contain payload `%s',\ngot=`%s'", tc.out, o) 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /stream/benchmark_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package stream_test 6 | 7 | import ( 8 | "net" 9 | "testing" 10 | 11 | "mellium.im/xmpp/stream" 12 | ) 13 | 14 | func BenchmarkSeeOtherHostError(b *testing.B) { 15 | ip := &net.IPAddr{IP: net.ParseIP("2001:db8::68")} 16 | b.ResetTimer() 17 | for n := 0; n < b.N; n++ { 18 | _ = stream.SeeOtherHostError(ip) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /stream/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package stream implements stream level functionality. 6 | // 7 | // The various stream errors defined by RFC 6120 § 4.9 are included as a 8 | // convenience, but most people will want to use the facilities of the 9 | // mellium.im/xmpp package and not create stream errors directly. 10 | package stream // import "mellium.im/xmpp/stream" 11 | 12 | // Namespaces used by XMPP streams and stream errors, provided as a convenience. 13 | const ( 14 | NS = "http://etherx.jabber.org/streams" 15 | NSError = "urn:ietf:params:xml:ns:xmpp-streams" 16 | ) 17 | -------------------------------------------------------------------------------- /stream/stream.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package stream 6 | 7 | import ( 8 | "encoding/xml" 9 | 10 | "mellium.im/xmpp/jid" 11 | ) 12 | 13 | // Info contains metadata extracted from a stream start token. 14 | type Info struct { 15 | Name xml.Name 16 | XMLNS string 17 | To jid.JID 18 | From jid.JID 19 | ID string 20 | Version Version 21 | Lang string 22 | } 23 | 24 | // FromStartElement sets the data in Info from the provided StartElement. 25 | func (i *Info) FromStartElement(s xml.StartElement) error { 26 | i.Name = s.Name 27 | for _, attr := range s.Attr { 28 | switch attr.Name { 29 | case xml.Name{Space: "", Local: "xmlns"}: 30 | i.XMLNS = attr.Value 31 | case xml.Name{Space: "", Local: "to"}: 32 | if err := (&i.To).UnmarshalXMLAttr(attr); err != nil { 33 | return ImproperAddressing 34 | } 35 | case xml.Name{Space: "", Local: "from"}: 36 | if err := (&i.From).UnmarshalXMLAttr(attr); err != nil { 37 | return ImproperAddressing 38 | } 39 | case xml.Name{Space: "", Local: "id"}: 40 | i.ID = attr.Value 41 | case xml.Name{Space: "", Local: "version"}: 42 | err := (&i.Version).UnmarshalXMLAttr(attr) 43 | if err != nil { 44 | return BadFormat 45 | } 46 | case xml.Name{Space: "xml", Local: "lang"}: 47 | i.Lang = attr.Value 48 | } 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /stream/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package stream 6 | 7 | import ( 8 | "encoding/xml" 9 | "errors" 10 | "fmt" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | // Common XMPP versions. 16 | var ( 17 | DefaultVersion = Version{1, 0} // The default version to send. 18 | EmptyVersion = Version{0, 9} // The value of a missing version attribute. 19 | ) 20 | 21 | // Version is a version of XMPP. 22 | type Version struct { 23 | Major uint8 24 | Minor uint8 25 | } 26 | 27 | // MustParseVersion parses a version string and panics if an error is returned. 28 | func MustParseVersion(s string) Version { 29 | v, err := ParseVersion(s) 30 | if err != nil { 31 | panic(err) 32 | } 33 | return v 34 | } 35 | 36 | // ParseVersion parses a string of the form "Major.Minor" into a Version struct 37 | // or returns an error. 38 | func ParseVersion(s string) (Version, error) { 39 | v := Version{} 40 | 41 | versions := strings.Split(s, ".") 42 | if len(versions) != 2 { 43 | return v, errors.New("XMPP version must have a single separator") 44 | } 45 | 46 | // Parse major version number 47 | major, err := strconv.ParseUint(versions[0], 10, 8) 48 | if err != nil { 49 | return v, err 50 | } 51 | v.Major = uint8(major) 52 | 53 | // Parse minor version number 54 | minor, err := strconv.ParseUint(versions[1], 10, 8) 55 | if err != nil { 56 | return v, err 57 | } 58 | v.Minor = uint8(minor) 59 | 60 | return v, nil 61 | } 62 | 63 | // Less compares the major and minor version numbers, returning true if a is 64 | // less than b. 65 | func (v Version) Less(b Version) bool { 66 | return v.Major < b.Major || (v.Major == b.Major && v.Minor < b.Minor) 67 | } 68 | 69 | // String returns the XMPP version in the form "Major.Minor". 70 | func (v Version) String() string { 71 | return fmt.Sprintf("%d.%d", v.Major, v.Minor) 72 | } 73 | 74 | // MarshalXMLAttr satisfies the MarshalerAttr interface and marshals the version 75 | // as an XML attribute using its string representation. 76 | func (v Version) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { 77 | return xml.Attr{Name: name, Value: v.String()}, nil 78 | } 79 | 80 | // UnmarshalXMLAttr satisfies the UnmarshalerAttr interface and unmarshals an 81 | // XML attribute into a valid XMPP version (or returns an error). 82 | func (v *Version) UnmarshalXMLAttr(attr xml.Attr) error { 83 | newVersion, err := ParseVersion(attr.Value) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | v.Major = newVersion.Major 89 | v.Minor = newVersion.Minor 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /styling/README.md: -------------------------------------------------------------------------------- 1 | # Message Styling Tests 2 | 3 | This package was written in part to act as a reference implementation for 4 | [XEP-0393: Message Styling]. 5 | To this end, the tests are designed to be exportable to a language agnostic 6 | format. 7 | For more information, see the file `export_test.go`. 8 | To export the tests run the following in this directory: 9 | 10 | go test -tags export 11 | 12 | This will result in the creation of the file `decoder_tests.json`. 13 | This file will be a JSON array of test cases. 14 | Each case has the following properties: 15 | 16 | - "Name": a string description of the test 17 | - "Input": the message styling string to parse 18 | - "Tokens": an array of tokens that result from parsing the string 19 | 20 | Each token has the following properties: 21 | 22 | - Mask: a numeric bitmask containing all of the styles applied to the token 23 | - Data: the subslice of the input string that was detected as a token 24 | - Info: any info string present at the start of pre-formatted blocks 25 | - Quote: the numeric quotation depth of the token if inside a block quote 26 | 27 | The values for "Mask" can be found in the "[constants]" section of the package 28 | documentation. 29 | 30 | ## Known Limitations 31 | 32 | The bitmask contains several styles, such as `BlockPreStart`, that are not part 33 | of the specification and may not be used by all implementations. 34 | These are to mark the start and end of spans and blocks and may be ignored if 35 | your implementation does not differentiate them. 36 | 37 | Long plain text spans may also be broken up at arbitrary intervals depending on 38 | the parser buffer length. 39 | For example, the string "one two" could be a single token "one two" or it could 40 | be broken up into the tokens "one " and "two" or "one" and " two" or even "on" 41 | and "e two" by the tests. 42 | These test cases should easily fit within any reasonable buffer, but if your 43 | implementation uses a smaller buffer size or breaks up long spans differently 44 | you may have to account for this when running these test cases. 45 | 46 | [XEP-0393: Message Styling]: https://xmpp.org/extensions/xep-0393.html 47 | [constants]: https://pkg.go.dev/mellium.im/xmpp/styling#pkg-constants 48 | -------------------------------------------------------------------------------- /styling/disable.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package styling 6 | 7 | import ( 8 | "encoding/xml" 9 | 10 | "mellium.im/xmlstream" 11 | "mellium.im/xmpp/stanza" 12 | ) 13 | 14 | const ( 15 | // NS is the message styling namespace, exported as a convenience. 16 | NS = "urn:xmpp:styling:0" 17 | ) 18 | 19 | // Unstyled is a type that can be added to messages to add a hint that will 20 | // disable styling. 21 | // When unmarshaled or marshaled its value indicates whether the unstyled hint 22 | // was or will be present in the message. 23 | type Unstyled struct { 24 | XMLName xml.Name `xml:"urn:xmpp:styling:0 unstyled"` 25 | Value bool 26 | } 27 | 28 | // TokenReader implements xmlstream.Marshaler. 29 | func (u Unstyled) TokenReader() xml.TokenReader { 30 | return xmlstream.Wrap( 31 | nil, 32 | xml.StartElement{Name: xml.Name{Space: NS, Local: "unstyled"}}, 33 | ) 34 | } 35 | 36 | // WriteXML implements xmlstream.WriterTo. 37 | func (u Unstyled) WriteXML(w xmlstream.TokenWriter) (int, error) { 38 | return xmlstream.Copy(w, u.TokenReader()) 39 | } 40 | 41 | // MarshalXML implements xml.Marshaler. 42 | func (u Unstyled) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { 43 | _, err := u.WriteXML(e) 44 | return err 45 | } 46 | 47 | // UnmarshalXML implements xml.Unmarshaler. 48 | func (u *Unstyled) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 49 | u.Value = start.Name.Space == NS && start.Name.Local == "unstyled" 50 | return d.Skip() 51 | } 52 | 53 | var ( 54 | clientInserter = xmlstream.Insert(xml.Name{Space: stanza.NSClient, Local: "message"}, Unstyled{Value: true}) 55 | serverInserter = xmlstream.Insert(xml.Name{Space: stanza.NSServer, Local: "message"}, Unstyled{Value: true}) 56 | ) 57 | 58 | // Disable is an xmlstream.Transformer that inserts a hint into any message read 59 | // through r that disables styling for the body of the message. 60 | func Disable(r xml.TokenReader) xml.TokenReader { 61 | return serverInserter(clientInserter(r)) 62 | } 63 | -------------------------------------------------------------------------------- /styling/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature"; DO NOT EDIT. 2 | 3 | package styling 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | -------------------------------------------------------------------------------- /styling/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package styling_test 6 | 7 | import ( 8 | "fmt" 9 | "html" 10 | "io" 11 | "strings" 12 | 13 | "mellium.im/xmpp/styling" 14 | ) 15 | 16 | func Example_html() { 17 | r := strings.NewReader(`The full title is 18 | _Twelfth Night, or What You Will_ 19 | but *most* people shorten it.`) 20 | d := styling.NewDecoder(r) 21 | 22 | var out strings.Builder 23 | out.WriteString("\n") 24 | for d.Next() { 25 | tok := d.Token() 26 | mask := d.Style() 27 | 28 | switch { 29 | case mask&styling.SpanEmphStart == styling.SpanEmphStart: 30 | out.WriteString("") 31 | out.Write(tok.Data) 32 | out.WriteString("") 33 | case mask&styling.SpanStrongStart == styling.SpanStrongStart: 34 | out.WriteString("") 35 | out.Write(tok.Data) 36 | out.WriteString("") 37 | case mask&styling.SpanEmphEnd == styling.SpanEmphEnd: 38 | out.WriteString("") 39 | out.Write(tok.Data) 40 | out.WriteString("") 41 | case mask&styling.SpanStrongEnd == styling.SpanStrongEnd: 42 | out.WriteString("") 43 | out.Write(tok.Data) 44 | out.WriteString("") 45 | // TODO: no other styles implemented to keep the example short. 46 | default: 47 | out.WriteString(html.EscapeString(string(tok.Data))) 48 | } 49 | } 50 | 51 | err := d.Err() 52 | if err != nil && err != io.EOF { 53 | out.WriteString("") 54 | out.WriteString(html.EscapeString(fmt.Sprintf("Error encountered while parsing tokens: %v", err))) 55 | out.WriteString("") 56 | } 57 | fmt.Println(out.String()) 58 | 59 | // Output: 60 | // 61 | // The full title is 62 | // _Twelfth Night, or What You Will_ 63 | // but *most* people shorten it. 64 | } 65 | -------------------------------------------------------------------------------- /styling/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build export 6 | // +build export 7 | 8 | package styling_test 9 | 10 | // This file is a tool that exports test data in JSON format for other libraries 11 | // and languages to use. 12 | // Running "go test -tags export" will cause the following TestMain function to 13 | // run which will spit out the tests to the file "decoder_tests.json" or 14 | // whatever filename is specified with the "-export" flag. 15 | 16 | import ( 17 | "encoding/json" 18 | "flag" 19 | "fmt" 20 | "os" 21 | "testing" 22 | 23 | "mellium.im/xmpp/styling" 24 | ) 25 | 26 | type jsonStyle struct { 27 | Mask styling.Style 28 | Data string 29 | Info string 30 | Quote uint 31 | } 32 | 33 | type jsonTestCase struct { 34 | Name string 35 | Input string 36 | Tokens []jsonStyle 37 | } 38 | 39 | func TestMain(m *testing.M) { 40 | var outName = "decoder_tests.json" 41 | flag.StringVar(&outName, "export", outName, "a filename to export JSON tests to") 42 | flag.Parse() 43 | 44 | fd, err := os.Create(outName) 45 | if err != nil { 46 | fmt.Fprintf(os.Stderr, "failed to open file: %v", err) 47 | os.Exit(1) 48 | } 49 | defer func() { 50 | if err := fd.Close(); err != nil { 51 | fmt.Fprintf(os.Stderr, "failed to close file: %v", err) 52 | os.Exit(1) 53 | } 54 | }() 55 | 56 | var jsonTestCases []jsonTestCase 57 | for _, tc := range decoderTestCases { 58 | newTC := jsonTestCase{ 59 | Name: tc.name, 60 | Input: tc.input, 61 | } 62 | for _, tok := range tc.toks { 63 | newTC.Tokens = append(newTC.Tokens, jsonStyle{ 64 | Mask: tok.Mask, 65 | Data: string(tok.Data), 66 | Info: string(tok.Info), 67 | Quote: tok.Quote, 68 | }) 69 | } 70 | jsonTestCases = append(jsonTestCases, newTC) 71 | } 72 | 73 | e := json.NewEncoder(fd) 74 | e.SetIndent("", "\t") 75 | err = e.Encode(jsonTestCases) 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /styling/fuzz_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package styling_test 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "io" 11 | "testing" 12 | 13 | "mellium.im/xmpp/styling" 14 | ) 15 | 16 | func FuzzParseDocument(f *testing.F) { 17 | f.Add([]byte{'*'}) 18 | f.Add([]byte{'_'}) 19 | f.Add([]byte{'`'}) 20 | f.Add([]byte("```")) 21 | f.Add([]byte{'~'}) 22 | f.Add([]byte{'>'}) 23 | f.Add([]byte{'\n'}) 24 | f.Fuzz(func(t *testing.T, doc []byte) { 25 | d := styling.NewDecoder(bytes.NewReader(doc)) 26 | n := 0 27 | for d.Next() { 28 | tok := d.Token() 29 | if !bytes.Equal([]byte(doc[n:n+len(tok.Data)]), tok.Data) { 30 | t.Fatalf("output bytes did not equal input bytes at %d for input:\n%v", n, doc) 31 | } 32 | n += len(tok.Data) 33 | } 34 | switch err := d.Err(); { 35 | case errors.Is(err, io.EOF): 36 | if n != len(doc) { 37 | t.Fatalf("got early EOF at %d", n) 38 | } 39 | case err == nil: 40 | default: 41 | t.Fatalf("error decoding: %v\nOriginal bytes:\n%v", err, doc) 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /styling/style_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Style"; DO NOT EDIT. 2 | 3 | package styling 4 | 5 | import "strconv" 6 | 7 | const _Style_name = "BlockPreBlockQuoteSpanEmphSpanStrongSpanStrikeSpanPreBlockPreStartBlockPreEndBlockQuoteStartBlockQuoteEndSpanEmphStartSpanEmphEndSpanStrongStartSpanStrongEndSpanStrikeStartSpanStrikeEndSpanPreStartSpanPreEnd" 8 | 9 | var _Style_map = map[Style]string{ 10 | 1: _Style_name[0:8], 11 | 2: _Style_name[8:18], 12 | 4: _Style_name[18:26], 13 | 8: _Style_name[26:36], 14 | 16: _Style_name[36:46], 15 | 32: _Style_name[46:53], 16 | 64: _Style_name[53:66], 17 | 128: _Style_name[66:77], 18 | 256: _Style_name[77:92], 19 | 512: _Style_name[92:105], 20 | 1024: _Style_name[105:118], 21 | 2048: _Style_name[118:129], 22 | 4096: _Style_name[129:144], 23 | 8192: _Style_name[144:157], 24 | 16384: _Style_name[157:172], 25 | 32768: _Style_name[172:185], 26 | 65536: _Style_name[185:197], 27 | 131072: _Style_name[197:207], 28 | } 29 | 30 | func (i Style) String() string { 31 | if str, ok := _Style_map[i]; ok { 32 | return str 33 | } 34 | return "Style(" + strconv.FormatInt(int64(i), 10) + ")" 35 | } 36 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build tools 6 | // +build tools 7 | 8 | package xmpp 9 | 10 | import ( 11 | _ "golang.org/x/tools/cmd/stringer" 12 | ) 13 | -------------------------------------------------------------------------------- /upload/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature"; DO NOT EDIT. 2 | 3 | package upload 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | -------------------------------------------------------------------------------- /upload/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:generate go run ../internal/genfeature 6 | 7 | // Package upload implements sending files by uploading them to an HTTP server. 8 | package upload // import "mellium.im/xmpp/upload" 9 | -------------------------------------------------------------------------------- /uri/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package uri 6 | 7 | var TestErrBadScheme = errBadScheme 8 | -------------------------------------------------------------------------------- /version/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature"; DO NOT EDIT. 2 | 3 | package version 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | -------------------------------------------------------------------------------- /version/version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package version_test 6 | 7 | import ( 8 | "context" 9 | "encoding/xml" 10 | "reflect" 11 | "strconv" 12 | "strings" 13 | "testing" 14 | 15 | "mellium.im/xmlstream" 16 | "mellium.im/xmpp/internal/xmpptest" 17 | "mellium.im/xmpp/jid" 18 | "mellium.im/xmpp/mux" 19 | "mellium.im/xmpp/stanza" 20 | "mellium.im/xmpp/version" 21 | ) 22 | 23 | var ( 24 | _ xmlstream.Marshaler = (*version.Query)(nil) 25 | _ xmlstream.WriterTo = (*version.Query)(nil) 26 | ) 27 | 28 | var marshalTests = [...]struct { 29 | in version.Query 30 | out string 31 | }{ 32 | 0: { 33 | in: version.Query{}, 34 | out: ``, 35 | }, 36 | 1: { 37 | in: version.Query{ 38 | Name: "name", 39 | Version: "ver", 40 | OS: "os", 41 | }, 42 | out: `nameveros`, 43 | }, 44 | } 45 | 46 | func TestMarshal(t *testing.T) { 47 | for i, tc := range marshalTests { 48 | t.Run(strconv.Itoa(i), func(t *testing.T) { 49 | t.Run("marshal", func(t *testing.T) { 50 | b, err := xml.Marshal(tc.in) 51 | if err != nil { 52 | t.Fatalf("error marshaling IQ: %v", err) 53 | } 54 | if string(b) != tc.out { 55 | t.Errorf("wrong output:\nwant=%s,\n got=%s", tc.out, b) 56 | } 57 | }) 58 | t.Run("encode", func(t *testing.T) { 59 | var buf strings.Builder 60 | e := xml.NewEncoder(&buf) 61 | _, err := tc.in.WriteXML(e) 62 | if err != nil { 63 | t.Fatalf("error writing XML: %v", err) 64 | } 65 | if err = e.Flush(); err != nil { 66 | t.Fatalf("error flushing XML: %v", err) 67 | } 68 | if out := buf.String(); out != tc.out { 69 | t.Errorf("wrong output:\nwant=%s,\n got=%s", tc.out, out) 70 | } 71 | }) 72 | }) 73 | } 74 | } 75 | 76 | func TestGet(t *testing.T) { 77 | query := version.Query{ 78 | Name: "name", 79 | Version: "ver", 80 | OS: "os", 81 | } 82 | m := mux.New(stanza.NSClient, version.Handle(query)) 83 | cs := xmpptest.NewClientServer(xmpptest.ServerHandler(m)) 84 | 85 | resp, err := version.Get(context.Background(), cs.Client, jid.JID{}) 86 | if err != nil { 87 | t.Fatalf("error querying version: %v", err) 88 | } 89 | expectedName := xml.Name{Space: version.NS, Local: "query"} 90 | if resp.XMLName != expectedName { 91 | t.Errorf("wrong XML name: want=%v, got=%v", expectedName, resp.XMLName) 92 | } 93 | resp.XMLName = xml.Name{} 94 | if !reflect.DeepEqual(resp, query) { 95 | t.Errorf("unexpected response: want=%v, got=%v", query, resp) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /websocket/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package websocket implements a WebSocket transport for XMPP. 6 | package websocket // import "mellium.im/xmpp/websocket" 7 | 8 | // Various constants used by this package, provided as a convenience. 9 | const ( 10 | // NS is the XML namespace used by the XMPP subprotocol framing. 11 | NS = "urn:ietf:params:xml:ns:xmpp-framing" 12 | 13 | // WSProtocol is the protocol string used during the WebSocket handshake. 14 | WSProtocol = "xmpp" 15 | ) 16 | -------------------------------------------------------------------------------- /websocket/negotiator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "context" 9 | "io" 10 | 11 | "mellium.im/xmpp" 12 | "mellium.im/xmpp/internal/wskey" 13 | "mellium.im/xmpp/stream" 14 | ) 15 | 16 | // Negotiator is like xmpp.NewNegotiator except that it uses the websocket 17 | // subprotocol. 18 | func Negotiator(cfg func(*xmpp.Session, *xmpp.StreamConfig) xmpp.StreamConfig) xmpp.Negotiator { 19 | xmppNegotiator := xmpp.NewNegotiator(cfg) 20 | return func(ctx context.Context, in, out *stream.Info, session *xmpp.Session, data interface{}) (xmpp.SessionState, io.ReadWriter, interface{}, error) { 21 | ctx = context.WithValue(ctx, wskey.Key{}, struct{}{}) 22 | return xmppNegotiator(ctx, in, out, session, data) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /xtime/disco.go: -------------------------------------------------------------------------------- 1 | // Code generated by "genfeature -receiver h Handler"; DO NOT EDIT. 2 | 3 | package xtime 4 | 5 | import ( 6 | "mellium.im/xmpp/disco/info" 7 | ) 8 | 9 | // A list of service discovery features that are supported by this package. 10 | var ( 11 | Feature = info.Feature{Var: NS} 12 | ) 13 | 14 | // ForFeatures implements info.FeatureIter. 15 | func (h Handler) ForFeatures(node string, f func(info.Feature) error) error { 16 | if node != "" { 17 | return nil 18 | } 19 | var err error 20 | err = f(Feature) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /xtime/integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build integration 6 | // +build integration 7 | 8 | package xtime_test 9 | 10 | import ( 11 | "context" 12 | "crypto/tls" 13 | "testing" 14 | "time" 15 | 16 | "mellium.im/sasl" 17 | "mellium.im/xmpp" 18 | "mellium.im/xmpp/internal/integration" 19 | "mellium.im/xmpp/internal/integration/ejabberd" 20 | "mellium.im/xmpp/internal/integration/jackal" 21 | "mellium.im/xmpp/internal/integration/prosody" 22 | "mellium.im/xmpp/xtime" 23 | ) 24 | 25 | func TestIntegrationRequestTime(t *testing.T) { 26 | prosodyRun := prosody.Test(context.TODO(), t, 27 | integration.Log(), 28 | prosody.ListenC2S(), 29 | ) 30 | prosodyRun(integrationRequestTime) 31 | 32 | ejabberdRun := ejabberd.Test(context.TODO(), t, 33 | integration.Log(), 34 | ejabberd.ListenC2S(), 35 | ) 36 | ejabberdRun(integrationRequestTime) 37 | 38 | jackalRun := jackal.Test(context.TODO(), t, 39 | integration.Log(), 40 | jackal.ListenC2S(), 41 | jackal.Modules("time"), 42 | ) 43 | jackalRun(integrationRequestTime) 44 | } 45 | 46 | func integrationRequestTime(ctx context.Context, t *testing.T, cmd *integration.Cmd) { 47 | j, pass := cmd.User() 48 | session, err := cmd.DialClient(ctx, j, t, 49 | xmpp.StartTLS(&tls.Config{ 50 | InsecureSkipVerify: true, 51 | }), 52 | xmpp.SASL("", pass, sasl.Plain, sasl.ScramSha256), 53 | xmpp.BindResource(), 54 | ) 55 | if err != nil { 56 | t.Fatalf("error connecting: %v", err) 57 | } 58 | go func() { 59 | err := session.Serve(nil) 60 | if err != nil { 61 | t.Logf("error from serve: %v", err) 62 | } 63 | }() 64 | tt, err := xtime.Get(ctx, session, session.RemoteAddr()) 65 | if err != nil { 66 | t.Errorf("error getting time: %v", err) 67 | } 68 | if now := time.Now().UTC().Format(time.RFC3339); tt.UTC().Format(time.RFC3339) != now { 69 | t.Errorf("wrong time: want=%v, got=%v", now, tt) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /xtime/time_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xtime_test 6 | 7 | import ( 8 | "encoding/xml" 9 | "fmt" 10 | "time" 11 | 12 | "mellium.im/xmpp/xtime" 13 | ) 14 | 15 | func ExampleTime() { 16 | t, _ := time.Parse(time.RFC3339, "2020-02-19T06:46:00-05:00") 17 | xt := xtime.Time{Time: t} 18 | 19 | o, _ := xml.Marshal(xt) 20 | fmt.Printf("%s\n", o) 21 | // Output: 22 | // 23 | } 24 | -------------------------------------------------------------------------------- /xtime/time_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Mellium Contributors. 2 | // Use of this source code is governed by the BSD 2-clause 3 | // license that can be found in the LICENSE file. 4 | 5 | package xtime_test 6 | 7 | import ( 8 | "context" 9 | "encoding/xml" 10 | "testing" 11 | "time" 12 | 13 | "mellium.im/xmlstream" 14 | "mellium.im/xmpp/internal/xmpptest" 15 | "mellium.im/xmpp/mux" 16 | "mellium.im/xmpp/stanza" 17 | "mellium.im/xmpp/xtime" 18 | ) 19 | 20 | var ( 21 | _ xml.Marshaler = xtime.Time{} 22 | _ xml.Unmarshaler = (*xtime.Time)(nil) 23 | _ xmlstream.Marshaler = xtime.Time{} 24 | _ xmlstream.WriterTo = xtime.Time{} 25 | _ xml.MarshalerAttr = xtime.Time{} 26 | _ xml.UnmarshalerAttr = (*xtime.Time)(nil) 27 | ) 28 | 29 | func TestRoundTrip(t *testing.T) { 30 | serverTime := time.Time{} 31 | serverTime = serverTime.Add(24 * time.Hour * 7 * 52) 32 | h := xtime.Handler{ 33 | TimeFunc: func() time.Time { 34 | return serverTime 35 | }, 36 | } 37 | m := mux.New(stanza.NSClient, xtime.Handle(h)) 38 | cs := xmpptest.NewClientServer( 39 | xmpptest.ServerHandler(m), 40 | ) 41 | 42 | respTime, err := xtime.Get(context.Background(), cs.Client, cs.Server.LocalAddr()) 43 | if err != nil { 44 | t.Errorf("unexpected error: %v", err) 45 | } 46 | 47 | if !serverTime.Equal(respTime) { 48 | t.Errorf("wrong time: want=%v, got=%v", serverTime, respTime) 49 | } 50 | } 51 | 52 | func TestAttrMarshal(t *testing.T) { 53 | zeroTime := time.Time{}.Add(24 * time.Hour) 54 | xt := xtime.Time{Time: zeroTime} 55 | name := xml.Name{Space: "example.net", Local: "foo"} 56 | attr, err := xt.MarshalXMLAttr(name) 57 | if err != nil { 58 | t.Fatalf("unexpected error marshaling attr: %v", err) 59 | } 60 | if attr.Name != name { 61 | t.Fatalf("wrong name for attr: want=%v, got=%v", name, attr.Name) 62 | } 63 | const expected = "0001-01-02T00:00:00Z" 64 | if attr.Value != expected { 65 | t.Fatalf("wrong value for attr: want=%v, got=%v", expected, attr.Value) 66 | } 67 | 68 | newTime := &xtime.Time{} 69 | err = newTime.UnmarshalXMLAttr(attr) 70 | if err != nil { 71 | t.Fatalf("unexpected error unmarshaling attr: %v", err) 72 | } 73 | if *newTime != xt { 74 | t.Fatalf("times don't match: want=%v, got=%v", xt, newTime) 75 | } 76 | } 77 | --------------------------------------------------------------------------------