~samwhited/xmpp

19a23c2c4f37b619c7f0b58c8a758f44bf621876 — Sam Whited 5 years ago 9fc8054
Add host-meta file lookup
1 files changed, 80 insertions(+), 21 deletions(-)

M lookup.go
M lookup.go => lookup.go +80 -21
@@ 5,6 5,8 @@
package xmpp

import (
	"context"
	"encoding/json"
	"encoding/xml"
	"net"
	"net/http"


@@ 12,15 14,17 @@ import (
	"path"
	"strings"

	"golang.org/x/net/context/ctxhttp"
	"mellium.im/xmpp/internal"
)

const (
	wsPrefix   = "_xmpp-client-websocket="
	boshPrefix = "_xmpp-client-xbosh="
	wsRel      = "urn:xmpp:alt-connections:websocket"
	boshRel    = "urn:xmpp:alt-connections:xbosh"
	hostMeta   = "/.well-known/host-meta"
	wsPrefix     = "_xmpp-client-websocket="
	boshPrefix   = "_xmpp-client-xbosh="
	wsRel        = "urn:xmpp:alt-connections:websocket"
	boshRel      = "urn:xmpp:alt-connections:xbosh"
	hostMetaXML  = "/.well-known/host-meta"
	hostMetaJSON = "/.well-known/host-meta.json"
)

var (


@@ 52,28 56,40 @@ func lookupWebsocketDNS(name string) (urls []string, err error) {
	return urls, err
}

func lookupWebsocketHostMeta(name string) (urls []string, err error) {
func lookupWebsocketHostMeta(ctx context.Context, name string) (urls []string, err error) {
	client := &http.Client{}
	url, err := url.Parse(name)
	if err != nil {
		return urls, err
	}
	url.Path = hostMeta
	resp, err := http.Get(path.Join(name, hostMeta))
	if err != nil {
		return urls, err
	}
	d := xml.NewDecoder(resp.Body)
	t, err := d.Token()
	url.Path = ""

	// Tokenize the response until we find the first <XRD/> element.
	var xrd internal.XRD
	for {
		if se, ok := t.(xml.StartElement); ok && se.Name == xrdName {
			if err = d.DecodeElement(&xrd, &se); err != nil {
				return urls, err
			}
			break
	ctx, cancel := context.WithCancel(ctx)

	var xrd *internal.XRD
	// Race! If one of the two goroutines does not error, we want that one. If
	// both error, or both are error free, we don't care.
	go func() {
		defer cancel()
		x, e := getHostMetaXML(ctx, client, url.String())
		if e != nil {
			err = e
			return
		}
		xrd, err = &x, e
	}()
	go func() {
		defer cancel()
		x, e := getHostMetaJSON(ctx, client, url.String())
		if e != nil {
			err = e
			return
		}
		xrd, err = &x, e
	}()

	if xrd == nil {
		return urls, err
	}

	for _, link := range xrd.Links {


@@ 83,3 99,46 @@ func lookupWebsocketHostMeta(name string) (urls []string, err error) {
	}
	return urls, err
}

func getHostMetaXML(
	ctx context.Context, client *http.Client, name string) (xrd internal.XRD, err error) {
	resp, err := ctxhttp.Get(ctx, client, path.Join(name, hostMetaXML))
	if err != nil {
		return xrd, err
	}
	d := xml.NewDecoder(resp.Body)

	t, err := d.Token()
	for {
		select {
		case <-ctx.Done():
			return xrd, ctx.Err()
		default:
			if se, ok := t.(xml.StartElement); ok && se.Name == xrdName {
				if err = d.DecodeElement(&xrd, &se); err != nil {
					return xrd, err
				}
				break
			}
		}
	}
}

func getHostMetaJSON(
	ctx context.Context, client *http.Client, name string) (xrd internal.XRD, err error) {
	resp, err := ctxhttp.Get(ctx, client, path.Join(name, hostMetaJSON))
	if err != nil {
		return xrd, err
	}

	if _, ok := <-ctx.Done(); ok {
		return xrd, ctx.Err()
	}

	d := json.NewDecoder(resp.Body)

	// TODO: We should probably tokenize this and have the ability to cancel
	// anywhere in between.
	err = d.Decode(&xrd)
	return xrd, err
}