~samwhited/xmpp

7f88af54f2b2f6303082df2f5959ca0f84e7bfaa — Sam Whited 5 years ago 6de3af6 + df6ceff
Subtree merge remote-tracking branch 'jid/master'
A jid/.gitignore => jid/.gitignore +39 -0
@@ 0,0 1,39 @@
# -*- mode: gitignore; -*-
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*

# Org-mode
.org-id-locations
*_archive

# flymake-mode
*_flymake.*

# eshell files
/eshell/history
/eshell/lastdir

# elpa packages
/elpa/
/target
/lib
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
*~


A jid/.pre-commit-config.yaml => jid/.pre-commit-config.yaml +5 -0
@@ 0,0 1,5 @@
-   repo: https://bitbucket.org/SamWhited/go-pre-commit.git
    sha: v0.0.2
    hooks:
      -   id: gofmt
      -   id: gotest

A jid/.travis.yml => jid/.travis.yml +4 -0
@@ 0,0 1,4 @@
language: go
go:
  - tip
  - 1.2

A jid/CONTRIBUTING.md => jid/CONTRIBUTING.md +15 -0
@@ 0,0 1,15 @@
# Contributing

Bugs, feature requests, and other discussions can be started by opening an
[issue][issues].

If you want to contribute a pull request, make sure you do the following first:

  - Format all code with `go fmt`
  - Write documentation for your code
  - Write tests for your code
  - Follow Go [best practices][bp]


[issues]: https://bitbucket.org/SamWhited/go-jid/issues
[bp]: http://talks.golang.org/2013/bestpractices.slide

A jid/LICENSE.md => jid/LICENSE.md +25 -0
@@ 0,0 1,25 @@
## BSD 2-clause license

Copyright © 2014 Sam Whited
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

A jid/Makefile => jid/Makefile +13 -0
@@ 0,0 1,13 @@
.SILENT:

.PHONY: test
test:
	go test -cover

.PHONY: benchmark
benchmark:
	go test -cover -bench . -benchmem -run 'Benchmark.*'

.PHONY: build
build:
	go build

A jid/README.md => jid/README.md +19 -0
@@ 0,0 1,19 @@
# Koiné

[![GoDoc](https://godoc.org/bitbucket.org/SamWhited/go-jid?status.svg)](https://godoc.org/bitbucket.org/SamWhited/go-jid)

**Koiné** is an XMPP JID address validation library for Go which aims to be
fully [RFC 7622][rfc7622] compliant (it's not yet).

To use it in your project, import it like so:

```go
import bitbucket.org/SamWhited/go-jid
```

## License

Use of this source code is governed by the BSD 2-clause license, a copy of
which can be found in the `LICENSE.md` file.

[rfc7622]: https://www.rfc-editor.org/rfc/rfc7622.txt

A jid/benchmark_test.go => jid/benchmark_test.go +60 -0
@@ 0,0 1,60 @@
// Copyright 2014 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package jid

import (
	"testing"
)

func BenchmarkSplit(b *testing.B) {
	for i := 0; i < b.N; i++ {
		SplitString("user@example.com/resource")
	}
}

func BenchmarkUnsafeFromString(b *testing.B) {
	for i := 0; i < b.N; i++ {
		UnsafeFromString("user@example.com/resource")
	}
}

func BenchmarkUnsafeFromStringIPv4(b *testing.B) {
	for i := 0; i < b.N; i++ {
		UnsafeFromString("user@127.0.0.1/resource")
	}
}

func BenchmarkUnsafeFromStringIPv6(b *testing.B) {
	for i := 0; i < b.N; i++ {
		UnsafeFromString("user@[::1]/resource")
	}
}

func BenchmarkUnsafeFromParts(b *testing.B) {
	for i := 0; i < b.N; i++ {
		UnsafeFromParts("user", "example.com", "resource")
	}
}

func BenchmarkCopy(b *testing.B) {
	j := &UnsafeJID{"user", "example.com", "resource"}
	for i := 0; i < b.N; i++ {
		j.Copy()
	}
}

func BenchmarkBare(b *testing.B) {
	j := &UnsafeJID{"user", "example.com", "resource"}
	for i := 0; i < b.N; i++ {
		j.Bare()
	}
}

func BenchmarkString(b *testing.B) {
	j := &UnsafeJID{"user", "example.com", "resource"}
	for i := 0; i < b.N; i++ {
		j.String()
	}
}

A jid/bitbucketci.yml => jid/bitbucketci.yml +7 -0
@@ 0,0 1,7 @@
image: golang:onbuild
pipeline:
  - job:
      script:
        - go-wrapper download
        - make test
        - make benchmark

A jid/doc.go => jid/doc.go +40 -0
@@ 0,0 1,40 @@
// Copyright 2014 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

// Package jid implements XMPP addresses (historically called "Jabber ID's" or
// "JID's") as described in RFC 7622.  The syntax for a JID is defined as
// follows using the Augmented Backus-Naur Form (ABNF):
//
//     jid          = [ localpart "@" ] domainpart [ "/" resourcepart ]
//     localpart    = 1*1023(userbyte)
//                    ;
//                    ; a "userbyte" is a byte used to represent a
//                    ; UTF-8 encoded Unicode code point that can be
//                    ; contained in a string that conforms to the
//                    ; UsernameCaseMapped profile of the PRECIS
//                    ; IdentifierClass defined in RFC 7613
//                    ;
//     domainpart   = IP-literal / IPv4address / ifqdn
//                    ;
//                    ; the "IPv4address" and "IP-literal" rules are
//                    ; defined in RFCs 3986 and 6874, respectively,
//                    ; and the first-match-wins (a.k.a. "greedy")
//                    ; algorithm described in Appendix B of RFC 3986
//                    ; applies to the matching process
//                    ;
//     ifqdn        = 1*1023(domainbyte)
//                    ;
//                    ; a "domainbyte" is a byte used to represent a
//                    ; UTF-8 encoded Unicode code point that can be
//                    ; contained in a string that conforms to RFC 5890
//                    ;
//     resourcepart = 1*1023(opaquebyte)
//                    ;
//                    ; an "opaquebyte" is a byte used to represent a
//                    ; UTF-8 encoded Unicode code point that can be
//                    ; contained in a string that conforms to the
//                    ; OpaqueString profile of the PRECIS
//                    ; FreeformClass defined in RFC 7613
//                    ;
package jid

A jid/enforcedjid.go => jid/enforcedjid.go +126 -0
@@ 0,0 1,126 @@
// Copyright 2014 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package jid

import (
	"encoding/xml"
	"errors"
	"unicode/utf8"

	"golang.org/x/net/idna"
)

// EnforcedJID represents an XMPP address (Jabber ID) comprising a localpart,
// domainpart, and resourcepart, that has undergone the PRECIS preparation and
// enforcement steps. This is normally what servers will want to use for all
// internal JID's.
type EnforcedJID struct {
	localpart    string
	domainpart   string
	resourcepart string
}

// EnforcedFromString constructs a new EnforcedJID from the given string
// representation.
func EnforcedFromString(s string) (*EnforcedJID, error) {
	localpart, domainpart, resourcepart, err := SplitString(s)
	if err != nil {
		return nil, err
	}
	return EnforcedFromParts(localpart, domainpart, resourcepart)
}

// EnforcedFromParts constructs a new EnforcedJID from the given localpart,
// domainpart, and resourcepart.
func EnforcedFromParts(localpart, domainpart, resourcepart string) (*EnforcedJID, error) {
	// Ensure that parts are valid UTF-8 (and short circuit the rest of the
	// process if they're not). We'll check the domainpart after performing
	// the IDNA ToUnicode operation.
	if !utf8.ValidString(localpart) || !utf8.ValidString(resourcepart) {
		return nil, errors.New("JID contains invalid UTF-8")
	}

	domainpart, err := idna.ToUnicode(domainpart)
	if err != nil {
		return nil, err
	}

	if !utf8.ValidString(domainpart) {
		return nil, errors.New("Domainpart contains invalid UTF-8")
	}

	// TODO: Do enforcement properly

	if err := commonChecks(localpart, domainpart, resourcepart); err != nil {
		return nil, err
	}

	return &EnforcedJID{
		localpart:    localpart,
		domainpart:   domainpart,
		resourcepart: resourcepart,
	}, nil
}

// Bare returns a copy of the Jid without a resourcepart. This is sometimes
// called a "bare" JID.
func (j *EnforcedJID) Bare() *EnforcedJID {
	return &EnforcedJID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: "",
	}
}

// Localpart gets the localpart of a JID (eg "username").
func (j *EnforcedJID) Localpart() string {
	return j.localpart
}

// Domainpart gets the domainpart of a JID (eg. "example.net").
func (j *EnforcedJID) Domainpart() string {
	return j.domainpart
}

// Resourcepart gets the resourcepart of a JID (eg. "someclient-abc123").
func (j *EnforcedJID) Resourcepart() string {
	return j.resourcepart
}

// Makes a copy of the given Jid. j.Equals(j.Copy()) will always return true.
func (j *EnforcedJID) Copy() *EnforcedJID {
	return &EnforcedJID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: j.resourcepart,
	}
}

// String converts an EnforcedJID to its string representation.
func (j *EnforcedJID) String() string {
	return stringify(j)
}

// Equal performs an octet-for-octet comparison with the given JID.
func (j *EnforcedJID) Equal(j2 JID) bool {
	return j.Localpart() == j2.Localpart() &&
		j.Domainpart() == j2.Domainpart() && j.Resourcepart() == j2.Resourcepart()
}

// MarshalXMLAttr satisfies the MarshalerAttr interface and marshals the JID as
// an XML attribute.
func (j *EnforcedJID) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
	return xml.Attr{Name: name, Value: j.String()}, nil
}

// UnmarshalXMLAttr satisfies the UnmarshalerAttr interface and unmarshals an
// XML attribute into a valid JID (or returns an error).
func (j *EnforcedJID) UnmarshalXMLAttr(attr xml.Attr) error {
	jid, err := EnforcedFromString(attr.Value)
	j.localpart = jid.Localpart()
	j.domainpart = jid.Domainpart()
	j.resourcepart = jid.Resourcepart()
	return err
}

A jid/jid.go => jid/jid.go +150 -0
@@ 0,0 1,150 @@
// Copyright 2014 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package jid

import (
	"encoding/xml"
	"errors"
	"net"
	"strings"
)

// JID defines methods that are common to all XMPP address (historically,
// "Jabber ID") implementations.
type JID interface {
	Localpart() string
	Domainpart() string
	Resourcepart() string

	String() string
	Equal(other JID) bool

	xml.MarshalerAttr
	xml.UnmarshalerAttr
}

// SplitString splits out the localpart, domainpart, and resourcepart from a
// string representation of a JID. The parts are not guaranteed to be valid, and
// SplitString only performs basic length validation on the individual parts.
func SplitString(s string) (localpart, domainpart, resourcepart string, err error) {

	// RFC 7622 §3.1.  Fundamentals:
	//
	//    Implementation Note: When dividing a JID into its component parts,
	//    an implementation needs to match the separator characters '@' and
	//    '/' before applying any transformation algorithms, which might
	//    decompose certain Unicode code points to the separator characters.
	//
	// so let's do that now. First we'll parse the domainpart using the rules
	// defined in §3.2:
	//
	//    The domainpart of a JID is the portion that remains once the
	//    following parsing steps are taken:
	//
	//    1.  Remove any portion from the first '/' character to the end of the
	//        string (if there is a '/' character present).
	parts := strings.SplitAfterN(
		s, "/", 2,
	)

	// If the resource part exists, make sure it isn't empty.
	if strings.HasSuffix(parts[0], "/") {
		if len(parts) == 2 && parts[1] != "" {
			resourcepart = parts[1]
		} else {
			err = errors.New("The resourcepart must be larger than 0 bytes")
			return
		}
	} else {
		resourcepart = ""
	}

	norp := strings.TrimSuffix(parts[0], "/")

	//    2.  Remove any portion from the beginning of the string to the first
	//        '@' character (if there is an '@' character present).

	nolp := strings.SplitAfterN(norp, "@", 2)

	if nolp[0] == "@" {
		err = errors.New("The localpart must be larger than 0 bytes")
		return
	}

	switch len(nolp) {
	case 1:
		domainpart = nolp[0]
		localpart = ""
	case 2:
		domainpart = nolp[1]
		localpart = strings.TrimSuffix(nolp[0], "@")
	}

	// We'll throw out any trailing dots on domainparts, since they're ignored:
	//
	//    If the domainpart includes a final character considered to be a label
	//    separator (dot) by [RFC1034], this character MUST be stripped from
	//    the domainpart before the JID of which it is a part is used for the
	//    purpose of routing an XML stanza, comparing against another JID, or
	//    constructing an XMPP URI or IRI [RFC5122].  In particular, such a
	//    character MUST be stripped before any other canonicalization steps
	//    are taken.

	domainpart = strings.TrimSuffix(domainpart, ".")

	return
}

func stringify(j JID) string {
	s := j.Domainpart()
	if lp := j.Localpart(); lp != "" {
		s = lp + "@" + s
	}
	if rp := j.Resourcepart(); rp != "" {
		s = s + "/" + rp
	}
	return s
}

func checkIP6String(domainpart string) error {
	// If the domainpart is a valid IPv6 address (with brackets), short circuit.
	if l := len(domainpart); l > 2 && strings.HasPrefix(domainpart, "[") &&
		strings.HasSuffix(domainpart, "]") {
		if ip := net.ParseIP(domainpart[1 : l-1]); ip == nil || ip.To4() != nil {
			return errors.New("Domainpart is not a valid IPv6 address")
		}
	}
	return nil
}

func commonChecks(localpart, domainpart, resourcepart string) error {
	l := len(localpart)
	if l > 1023 {
		return errors.New("The localpart must be smaller than 1024 bytes")
	}

	// RFC 7622 §3.3.1 provides a small table of characters which are still not
	// allowed in localpart's even though the IdentifierClass base class and the
	// UsernameCaseMapped profile don't forbid them; remove them here.
	if strings.ContainsAny(localpart, "\"&'/:<>@") {
		return errors.New("Localpart contains forbidden characters")
	}

	l = len(resourcepart)
	if l > 1023 {
		return errors.New("The resourcepart must be smaller than 1024 bytes")
	}

	l = len(domainpart)
	if l < 1 || l > 1023 {
		return errors.New("The domainpart must be between 1 and 1023 bytes")
	}

	if err := checkIP6String(domainpart); err != nil {
		return err
	}

	return nil
}

A jid/preparedjid.go => jid/preparedjid.go +137 -0
@@ 0,0 1,137 @@
// Copyright 2014 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package jid

import (
	"encoding/xml"
	"errors"
	"unicode/utf8"

	"golang.org/x/net/idna"
)

// PreparedJID represents an XMPP address (Jabber ID) comprising a localpart,
// domainpart, and resourcepart, that has undergone the PRECIS preparation step.
// This is normally what clients will want to use for all internal JID's.
type PreparedJID struct {
	localpart    string
	domainpart   string
	resourcepart string
}

// PreparedFromString constructs a new PreparedJID from the given string
// representation.
func PreparedFromString(s string) (*PreparedJID, error) {
	localpart, domainpart, resourcepart, err := SplitString(s)
	if err != nil {
		return nil, err
	}
	return PreparedFromParts(localpart, domainpart, resourcepart)
}

// PreparedFromParts constructs a new PreparedJID from the given localpart,
// domainpart, and resourcepart.
func PreparedFromParts(localpart, domainpart, resourcepart string) (*PreparedJID, error) {
	// Ensure that parts are valid UTF-8 (and short circuit the rest of the
	// process if they're not). We'll check the domainpart after performing
	// the IDNA ToUnicode operation.
	if !utf8.ValidString(localpart) || !utf8.ValidString(resourcepart) {
		return nil, errors.New("JID contains invalid UTF-8")
	}

	// RFC 7622 §3.2.1.  Preparation
	//
	//    An entity that prepares a string for inclusion in an XMPP domainpart
	//    slot MUST ensure that the string consists only of Unicode code points
	//    that are allowed in NR-LDH labels or U-labels as defined in
	//    [RFC5890].  This implies that the string MUST NOT include A-labels as
	//    defined in [RFC5890]; each A-label MUST be converted to a U-label
	//    during preparation of a string for inclusion in a domainpart slot.
	//
	// While we're not doing preparation yet, we're also going to store all JIDs
	// as Unicode strings, so let's go ahead and do this (even for Unsafe JID's).

	domainpart, err := idna.ToUnicode(domainpart)
	if err != nil {
		return nil, err
	}

	if !utf8.ValidString(domainpart) {
		return nil, errors.New("Domainpart contains invalid UTF-8")
	}

	// TODO: Do preparation properly

	if err := commonChecks(localpart, domainpart, resourcepart); err != nil {
		return nil, err
	}

	return &PreparedJID{
		localpart:    localpart,
		domainpart:   domainpart,
		resourcepart: resourcepart,
	}, nil
}

// Bare returns a copy of the Jid without a resourcepart. This is sometimes
// called a "bare" JID.
func (j *PreparedJID) Bare() *PreparedJID {
	return &PreparedJID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: "",
	}
}

// Localpart gets the localpart of a JID (eg "username").
func (j *PreparedJID) Localpart() string {
	return j.localpart
}

// Domainpart gets the domainpart of a JID (eg. "example.net").
func (j *PreparedJID) Domainpart() string {
	return j.domainpart
}

// Resourcepart gets the resourcepart of a JID (eg. "someclient-abc123").
func (j *PreparedJID) Resourcepart() string {
	return j.resourcepart
}

// Makes a copy of the given Jid. j.Equals(j.Copy()) will always return true.
func (j *PreparedJID) Copy() *PreparedJID {
	return &PreparedJID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: j.resourcepart,
	}
}

// String converts an PreparedJID to its string representation.
func (j *PreparedJID) String() string {
	return stringify(j)
}

// Equal performs an octet-for-octet comparison with the given JID.
func (j *PreparedJID) Equal(j2 JID) bool {
	return j.Localpart() == j2.Localpart() &&
		j.Domainpart() == j2.Domainpart() && j.Resourcepart() == j2.Resourcepart()
}

// MarshalXMLAttr satisfies the MarshalerAttr interface and marshals the JID as
// an XML attribute.
func (j *PreparedJID) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
	return xml.Attr{Name: name, Value: j.String()}, nil
}

// UnmarshalXMLAttr satisfies the UnmarshalerAttr interface and unmarshals an
// XML attribute into a valid JID (or returns an error).
func (j *PreparedJID) UnmarshalXMLAttr(attr xml.Attr) error {
	jid, err := PreparedFromString(attr.Value)
	j.localpart = jid.Localpart()
	j.domainpart = jid.Domainpart()
	j.resourcepart = jid.Resourcepart()
	return err
}

A jid/preparedjid_test.go => jid/preparedjid_test.go +35 -0
@@ 0,0 1,35 @@
// Copyright 2015 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.
package jid

import (
	"testing"
)

// PreparedJID's cannot contain invalid UTF8 in the localpart.
func TestNewInvalidUtf8Localpart(t *testing.T) {
	invalid := string([]byte{0xff, 0xfe, 0xfd})
	_, err := PreparedFromString(invalid + "@example.com/resourcepart")
	if err == nil {
		t.FailNow()
	}
}

// PreparedJID's cannot contain invalid UTF8 in the domainpart.
func TestNewInvalidUtf8Domainpart(t *testing.T) {
	invalid := string([]byte{0xff, 0xfe, 0xfd})
	_, err := PreparedFromString("example@" + invalid + "/resourcepart")
	if err == nil {
		t.FailNow()
	}
}

// PreparedJID's cannot contain invalid UTF8 in the resourcepart.
func TestNewInvalidUtf8Resourcepart(t *testing.T) {
	invalid := string([]byte{0xff, 0xfe, 0xfd})
	_, err := PreparedFromString("example@example.com/" + invalid)
	if err == nil {
		t.FailNow()
	}
}

A jid/unsafejid.go => jid/unsafejid.go +105 -0
@@ 0,0 1,105 @@
// Copyright 2014 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package jid

import (
	"encoding/xml"
)

// UnsafeJID represents an XMPP address (Jabber ID) comprising a localpart,
// domainpart, and resourcepart, that is not Unicode safe.
type UnsafeJID struct {
	localpart    string
	domainpart   string
	resourcepart string
}

// UnsafeFromString constructs a new UnsafeJID from the given string
// representation. The string may be any valid bare or full JID including
// raw domain names, IP literals, or hosts.
func UnsafeFromString(s string) (*UnsafeJID, error) {
	localpart, domainpart, resourcepart, err := SplitString(s)
	if err != nil {
		return nil, err
	}
	return UnsafeFromParts(localpart, domainpart, resourcepart)
}

// UnsafeFromParts constructs a new UnsafeJID from the given localpart,
// domainpart, and resourcepart. The only required part is the domainpart
// ('example.net' and 'hostname' are valid Jids).
func UnsafeFromParts(localpart, domainpart, resourcepart string) (*UnsafeJID, error) {

	if err := commonChecks(localpart, domainpart, resourcepart); err != nil {
		return nil, err
	}

	return &UnsafeJID{
		localpart:    localpart,
		domainpart:   domainpart,
		resourcepart: resourcepart,
	}, nil
}

// Bare returns a copy of the Jid without a resourcepart. This is sometimes
// called a "bare" JID.
func (j *UnsafeJID) Bare() *UnsafeJID {
	return &UnsafeJID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: "",
	}
}

// Localpart gets the localpart of a JID (eg "username").
func (j *UnsafeJID) Localpart() string {
	return j.localpart
}

// Domainpart gets the domainpart of a JID (eg. "example.net").
func (j *UnsafeJID) Domainpart() string {
	return j.domainpart
}

// Resourcepart gets the resourcepart of a JID (eg. "someclient-abc123").
func (j *UnsafeJID) Resourcepart() string {
	return j.resourcepart
}

// Makes a copy of the given Jid. j.Equals(j.Copy()) will always return true.
func (j *UnsafeJID) Copy() *UnsafeJID {
	return &UnsafeJID{
		localpart:    j.localpart,
		domainpart:   j.domainpart,
		resourcepart: j.resourcepart,
	}
}

// String converts an UnsafeJID to its string representation.
func (j *UnsafeJID) String() string {
	return stringify(j)
}

// Equal performs an octet-for-octet comparison with the given JID.
func (j *UnsafeJID) Equal(j2 JID) bool {
	return j.Localpart() == j2.Localpart() &&
		j.Domainpart() == j2.Domainpart() && j.Resourcepart() == j2.Resourcepart()
}

// MarshalXMLAttr satisfies the MarshalerAttr interface and marshals the JID as
// an XML attribute.
func (j *UnsafeJID) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
	return xml.Attr{Name: name, Value: j.String()}, nil
}

// UnmarshalXMLAttr satisfies the UnmarshalerAttr interface and unmarshals an
// XML attribute into a valid JID (or returns an error).
func (j *UnsafeJID) UnmarshalXMLAttr(attr xml.Attr) error {
	jid, err := UnsafeFromString(attr.Value)
	j.localpart = jid.Localpart()
	j.domainpart = jid.Domainpart()
	j.resourcepart = jid.Resourcepart()
	return err
}

A jid/unsafejid_test.go => jid/unsafejid_test.go +197 -0
@@ 0,0 1,197 @@
// Copyright 2015 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.
package jid

import (
	"bytes"
	"encoding/xml"
	"testing"
)

// Ensure that JID parts are split properly.
func TestValidPartsFromString(t *testing.T) {
	decompositions := [][]string{
		{"lp@dp/rp", "lp", "dp", "rp"},
		{"dp/rp", "", "dp", "rp"},
		{"dp", "", "dp", ""},
		{"lp@dp//rp", "lp", "dp", "/rp"},
		{"lp@dp/rp/", "lp", "dp", "rp/"},
		{"lp@dp/@rp/", "lp", "dp", "@rp/"},
		{"lp@dp/lp@dp/rp", "lp", "dp", "lp@dp/rp"},
		{"dp//rp", "", "dp", "/rp"},
		{"dp/rp/", "", "dp", "rp/"},
		{"dp/@rp/", "", "dp", "@rp/"},
		{"dp/lp@dp/rp", "", "dp", "lp@dp/rp"},
		{"₩", "", "₩", ""},
	}
	for _, d := range decompositions {
		lp, dp, rp, err := SplitString(d[0])
		if err != nil || lp != d[1] || dp != d[2] || rp != d[3] {
			t.FailNow()
		}
	}
}

// Ensure that JIDs that are too long return an error.
func TestLongParts(t *testing.T) {
	// Generate a part that is too long.
	pb := bytes.NewBuffer(make([]byte, 0, 1024))
	for i := 0; i < 64; i++ {
		pb.WriteString("aaaaaaaaaaaaaaaa")
	}
	ps := pb.String()
	jids := []string{
		ps + "@example.com/test",
		"lp@" + ps + "/test",
		"lp@example.com/" + ps,
		ps + "@" + ps + "/" + ps,
	}
	for _, d := range jids {
		if _, _, _, err := SplitString(d); err != nil {
			t.FailNow()
		}
	}
}

// Trying to create a JID with an empty localpart should error.
func TestNewEmptyLocalpart(t *testing.T) {
	_, err := UnsafeFromString("@example.com/resourcepart")
	if err == nil {
		t.FailNow()
	}
}

// Trying to create a JID with no localpart should work.
func TestNewNoLocalpart(t *testing.T) {
	jid, err := UnsafeFromString("example.com/resourcepart")
	if err != nil || jid.Localpart() != "" {
		t.FailNow()
	}
}

// Trying to create a JID with no domainpart should error.
func TestNewNoDomainpart(t *testing.T) {
	_, err := UnsafeFromString("text@/resourcepart")
	if err == nil {
		t.FailNow()
	}
}

// Trying to create a JID with no anything should error.
func TestNewNoAnything(t *testing.T) {
	_, err := UnsafeFromString("@/")
	if err == nil {
		t.FailNow()
	}
}

// Trying to create a JID from an empty string should error.
func TestNewEmptyString(t *testing.T) {
	_, err := UnsafeFromString("")
	if err == nil {
		t.FailNow()
	}
}

// Trying to create a JID with '@' or '/' in the resourcepart should work.
func TestNewJIDInResourcepart(t *testing.T) {
	_, err := UnsafeFromString("this/is@/fine")
	if err != nil {
		t.FailNow()
	}
}

// Trying to create a JID with an empty resource part should error.
func TestNewEmptyResourcepart(t *testing.T) {
	_, err := UnsafeFromString("text@example.com/")
	if err == nil {
		t.FailNow()
	}
}

// Trying to create a new bare JID (no resource part) should work.
func TestNewBareUnsafeJID(t *testing.T) {
	jid, err := UnsafeFromString("barejid@example.com")
	if err != nil || jid.Resourcepart() != "" {
		t.FailNow()
	}
}

// Creating a new JID from a valid JID string should work and contain all the
// correct parts.
func TestNewValid(t *testing.T) {
	s := "jid@example.com/resourcepart"
	jid, err := UnsafeFromString(s)
	if err != nil {
		t.FailNow()
	}
	switch {
	case err != nil:
		t.FailNow()
	case jid.Localpart() != "jid":
		t.FailNow()
	case jid.Domainpart() != "example.com":
		t.FailNow()
	case jid.Resourcepart() != "resourcepart":
		t.FailNow()
	}
}

// Two identical JIDs should be equal.
func TestEqualJIDs(t *testing.T) {
	jid := &UnsafeJID{"newjid", "example.com", "equal"}
	jid2 := &UnsafeJID{"newjid", "example.com", "equal"}
	if !jid.Equal(jid2) {
		t.FailNow()
	}
}

// An UnsafeJID should equal a copy of itself.
func TestCopy(t *testing.T) {
	j := &UnsafeJID{"newjid", "example.com", "equal"}
	if !j.Equal(j.Copy()) {
		t.FailNow()
	}
}

// Two different JIDs should not be equal.
func TestNotEqualJIDs(t *testing.T) {
	jid := &UnsafeJID{"newjid", "example.com", "notequal"}
	jid2 := &UnsafeJID{"newjid2", "example.com", "notequal"}
	if jid.Equal(jid2) {
		t.FailNow()
	}
	jid = &UnsafeJID{"newjid", "example.com", "notequal"}
	jid2 = &UnsafeJID{"newjid", "example.net", "notequal"}
	if jid.Equal(jid2) {
		t.FailNow()
	}
	jid = &UnsafeJID{"newjid", "example.com", "notequal"}
	jid2 = &UnsafeJID{"newjid", "example.com", "notequal2"}
	if jid.Equal(jid2) {
		t.FailNow()
	}
}

// &UnsafeJIDs should be marshalable to an XML attribute
func TestMarshal(t *testing.T) {
	jid := &UnsafeJID{"newjid", "example.com", "marshal"}
	attr, err := jid.MarshalXMLAttr(xml.Name{Space: "", Local: "to"})

	if err != nil || attr.Name.Local != "to" || attr.Name.Space != "" || attr.Value != "newjid@example.com/marshal" {
		t.FailNow()
	}
}

// &UnsafeJIDs should be unmarshalable from an XML attribute
func TestUnmarshal(t *testing.T) {
	jid := &UnsafeJID{}
	err := jid.UnmarshalXMLAttr(
		xml.Attr{xml.Name{"space", ""}, "newjid@example.com/unmarshal"},
	)

	if err != nil || jid.Localpart() != "newjid" || jid.Domainpart() != "example.com" || jid.Resourcepart() != "unmarshal" {
		t.FailNow()
	}
}