Exemple #1
0
func verifyDocs(origData, faksData []byte) (errs []error) {
	orig, faks := xmlx.New(), xmlx.New()
	err := orig.LoadBytes(origData, nil)
	if err == nil {
		if err = faks.LoadBytes(faksData, nil); err == nil {
			errs = verifyNode(orig.Root, faks.Root)
		}
	}
	return
}
Exemple #2
0
func getRoster(msg string) (map[string]*Contact, os.Error) {
	contactMap := make(map[string]*Contact)

	xmlDoc := xmlx.New()
	if err := xmlDoc.LoadString(msg); err != nil {
		logError(err)
		return nil, err
	}

	/*
		<iq to='[email protected]/balcony' type='result' id='roster_1'>
		  <query xmlns='jabber:iq:roster'>
			<item jid='*****@*****.**'
			      name='Romeo'
			      subscription='both'>
			  <group>Friends</group>
			</item>
		  </query>
		</iq>
	*/
	nodes := xmlDoc.SelectNodes("jabber:iq:roster", "item")
	for _, node := range nodes {
		tempContact := new(Contact)
		tempContact.Name = node.GetAttr("", "name")
		tempContact.Subscription = node.GetAttr("", "subscription")
		tempContact.JID = node.GetAttr("", "jid")
		tempContact.Show = "offline"
		tempContact.Status = ""
		contactMap[tempContact.JID] = tempContact
	}

	return contactMap, nil
}
Exemple #3
0
func (cb *CnBeta) Success(buf []byte) {
	doc := xmlx.New()
	if err := doc.LoadBytes(buf, nil); err != nil {
		cb.Failure(err)
		return
	}
	cb.listener.OnSuccess(parser(doc))
	cb.listener.OnEnd()
}
Exemple #4
0
// Fetch retrieves the feed's content from the []byte
//
// The charset parameter overrides the xml decoder's CharsetReader.
// This allows us to specify a custom character encoding conversion
// routine when dealing with non-utf8 input. Supply 'nil' to use the
// default from Go's xml package.
func (this *Feed) FetchBytes(uri string, content []byte, charset xmlx.CharsetFunc) (err error) {
	this.Url = uri

	doc := xmlx.New()

	if err = doc.LoadBytes(content, charset); err != nil {
		return
	}

	return this.makeFeed(doc)
}
Exemple #5
0
// Fetch retrieves the feed's latest content if necessary.
//
// The charset parameter overrides the xml decoder's CharsetReader.
// This allows us to specify a custom character encoding conversion
// routine when dealing with non-utf8 input. Supply 'nil' to use the
// default from Go's xml package.
//
// The client parameter allows the use of arbitrary network connections, for
// example the Google App Engine "URL Fetch" service.
func (this *Feed) FetchClient(uri string, client *http.Client, charset xmlx.CharsetFunc) (err error) {
	if !this.CanUpdate() {
		return
	}

	this.Url = uri
	doc := xmlx.New()

	if err = doc.LoadUriClient(uri, client, charset); err != nil {
		return
	}

	return this.makeFeed(doc)
}
func main() {
	doc := xmlx.New()
	err := doc.LoadUri("https://github.com/sbhackerspace/" + "sbhx-snippets" + "/commits/master.atom")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
	} else {
		children := doc.SelectNode("", "").Children
		fmt.Printf("Num children: %v\n", len(children))
		for i, child := range children {
			fmt.Printf("Child %v: %v\n", i, child.Children)
			fmt.Printf("\n\n")
		}
	}
}
Exemple #7
0
func Parse(r io.Reader, charset xmlx.CharsetFunc) (chs []*Channel, err error) {
	doc := xmlx.New()

	if err = doc.LoadStream(r, charset); err != nil {
		return
	}

	format, version := GetVersionInfo(doc)
	if ok := testVersions(format, version); !ok {
		err = errors.New(fmt.Sprintf("Unsupported feed: %s, version: %+v", format, version))
		return
	}

	return buildFeed(format, doc)
}
Exemple #8
0
/**
 * For each feature type, return a slice of values
 */
func getFeatures(msg string) (map[string][]string, os.Error) {
	keyValueMap := make(map[string][]string)

	xmlDoc := xmlx.New()
	if err := xmlDoc.LoadString(msg); err != nil {
		logError(err)
		return nil, err
	}

	/*
		<stream:features>
		  <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
			<mechanism>DIGEST-MD5</mechanism>
			<mechanism>PLAIN</mechanism>
		  </mechanisms>
		</stream:features>
	*/
	nodes := xmlDoc.SelectNodes(nsSASL, "mechanism")
	for _, node := range nodes {
		keyValueMap["mechanism"] = append(keyValueMap["mechanism"], node.Value)
		logVerbose("mechanism: %s", node.Value)
	}

	/*
		stream:features>
		  <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
		</stream:features>
	*/
	nodes = xmlDoc.SelectNodes(nsBind, "bind")
	for _, node := range nodes {
		keyValueMap["bind"] = append(keyValueMap["bind"], node.Value)
		logVerbose("bind: %s", node.Value)
	}

	/*
		stream:features>
		  <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
		</stream:features>
	*/
	nodes = xmlDoc.SelectNodes(nsSession, "session")
	for _, node := range nodes {
		keyValueMap["session"] = append(keyValueMap["session"], node.Value)
		logVerbose("session: %s", node.Value)
	}

	return keyValueMap, nil
}
Exemple #9
0
func Export(object export.Exportable) error {
	outFile := fmt.Sprintf("%v.dae", object.GetName())
	ctx := Context{
		Object:   xmlx.New(),
		imageIds: make(map[string]string),
	}

	ctx.Object.Root = xmlx.NewNode(xmlx.NT_ROOT)
	root := ctx.Object.Root

	collada := addChild(root, "COLLADA",
		Attribs{"xmlns": "http://www.collada.org/2005/11/COLLADASchema", "version": "1.4.1"}, "")

	asset := addChild(collada, "asset", nil, "")
	_ = addChild(asset, "unit", Attribs{"meter": "1", "name": "meter"}, "")
	_ = addChild(asset, "up_axis", nil, "Y_UP")

	ctx.libImages = addChild(collada, "library_images", nil, "")
	ctx.libMaterials = addChild(collada, "library_materials", nil, "")
	ctx.libEffects = addChild(collada, "library_effects", nil, "")
	ctx.libGeometries = addChild(collada, "library_geometries", nil, "")

	ctx.libScenes = addChild(collada, "library_visual_scenes", nil, "")
	ctx.scene = addChild(ctx.libScenes, "visual_scene", Attribs{"id": "Scene",
		"name": object.GetName()}, "")

	scenes := addChild(collada, "scene", nil, "")
	_ = addChild(scenes, "instance_visual_scene", Attribs{"url": "#Scene"}, "")

	for _, model := range object.GetModels() {
		var err error
		if err = ExportMaterials(&ctx, model); err != nil {
			return err
		}
		if err = ExportGeometries(&ctx, model); err != nil {
			return err
		}
	}

	if err := ctx.Object.SaveFile(outFile); err != nil {
		return err
	}

	return nil
}
// Fetch retrieves the feed's latest content if necessary.
//
// The charset parameter overrides the xml decoder's CharsetReader.
// This allows us to specify a custom character encoding conversion
// routine when dealing with non-utf8 input. Supply 'nil' to use the
// default from Go's xml package.
//
// The client parameter allows the use of arbitrary network connections, for
// example the Google App Engine "URL Fetch" service.
func (this *Feed) FetchClient(uri string, client *http.Client, charset xmlx.CharsetFunc) (err error) {
	if !this.CanUpdate() {
		return
	}

	this.lastupdate = time.Now().UTC()
	this.Url = uri
	doc := xmlx.New()

	if err = doc.LoadUriClient(uri, client, charset); err != nil {
		return
	}

	if err = this.makeFeed(doc); err == nil {
		// Only if fetching and parsing succeeded.
		this.ignoreCacheOnce = false
	}

	return
}
Exemple #11
0
// Fetch retrieves the feed's latest content if necessary.
//
// The charset parameter overrides the xml decoder's CharsetReader.
// This allows us to specify a custom character encoding conversion
// routine when dealing with non-utf8 input. Supply 'nil' to use the
// default from Go's xml package.
func (this *Feed) Fetch(uri string, charset xmlx.CharsetFunc) (err error) {
	if !this.CanUpdate() {
		return
	}

	this.Url = uri

	// Extract type and version of the feed so we can have the appropriate
	// function parse it (rss 0.91, rss 0.92, rss 2, atom etc).
	doc := xmlx.New()

	if err = doc.LoadUri(uri, charset); err != nil {
		return
	}
	this.Type, this.Version = this.GetVersionInfo(doc)

	if ok := this.testVersions(); !ok {
		err = errors.New(fmt.Sprintf("Unsupported feed: %s, version: %+v", this.Type, this.Version))
		return
	}

	chancount := len(this.Channels)
	if err = this.buildFeed(doc); err != nil || len(this.Channels) == 0 {
		return
	}

	// Notify host of new channels
	if chancount != len(this.Channels) && this.chanhandler != nil {
		this.chanhandler(this, this.Channels[chancount:])
	}

	// reset cache timeout values according to feed specified values (TTL)
	if this.EnforceCacheLimit && this.CacheTimeout < this.Channels[0].TTL {
		this.CacheTimeout = this.Channels[0].TTL
	}

	return
}
Exemple #12
0
func getMessage(msg string) (MessageUpdate, os.Error) {
	var tempMessage MessageUpdate

	xmlDoc := xmlx.New()
	if err := xmlDoc.LoadString(msg); err != nil {
		logError(err)
		return tempMessage, err
	}

	/*
		<message
			to='[email protected]/orchard'
			from='[email protected]/balcony'
			type='chat'
			xml:lang='en'>
		  <body>Art thou not Romeo, and a Montague?</body>
		  <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread>
		</message>
	*/
	node := xmlDoc.SelectNode("", "message")
	if node != nil {
		tempMessage.JID = node.GetAttr("", "from")
		tempMessage.Type = node.GetAttr("", "type")
		tempMessage.Body = node.GetValue("", "body")

		node = xmlDoc.SelectNode(nsChatstates, "composing")
		if node != nil {
			tempMessage.State = "composing"
		} else {
			node = xmlDoc.SelectNode(nsChatstates, "active")
			if node != nil {
				tempMessage.State = "active"
			}
		}
	}

	return tempMessage, nil
}
func main() {
	repo_names := []string{"sbhx-snippets", "sbhx-ircbot", "sbhx-rov",
		"sbhx-sicp"} //, "sbhx-androidapp", "sbhx-projecteuler"
	for _, repo := range repo_names[:1] {
		result, err := ioutil.ReadFile(repo)
		//defer ioutil.CloseFile(repo) // method doesn't exist
		if err != nil {
			fmt.Printf("%v\n", err)
		} else {
			//
			// Parse XML
			//
			doc := xmlx.New()
			if err = doc.LoadFile(string(result)); err != nil {
				fmt.Printf("Error: %v\n", err)
			} else {
				fmt.Printf("Root:\n")
				_ = doc.SelectNode("", "TreeRoot")
				_ = doc.SelectNodes("", "item")
			}
		}
	}
}
Exemple #14
0
func getPresenceUpdates(msg string) ([]PresenceUpdate, os.Error) {
	var updates []PresenceUpdate
	var tempUpdate PresenceUpdate

	xmlDoc := xmlx.New()
	if err := xmlDoc.LoadString(msg); err != nil {
		logError(err)
		return nil, err
	}

	/*
		<presence from='[email protected]/balcony' to='[email protected]/orchard' xml:lang='en'>
			<show>away</show>
			<status>be right back</status>
			<priority>0</priority>
			<x xmlns="vcard-temp:x:update">
				<photo>8668b9b00eeb2e3a51ea5758e7cff9f7c5780309</photo>
			</x>
		</presence>
	*/
	nodes := xmlDoc.SelectNodes("", "presence")
	for _, node := range nodes {
		//sometimes jid in presence update comes with /resource, split it off
		tempUpdate.JID = (strings.Split(node.GetAttr("", "from"), "/", -1))[0]
		tempUpdate.Type = node.GetAttr("", "type")
		tempUpdate.Show = node.GetValue("", "show")
		tempUpdate.Status = node.GetValue("", "status")
		//photo present? http://xmpp.org/extensions/xep-0153.html
		if tempnode := node.SelectNode(nsVcardUpdate, "x"); tempnode != nil {
			tempUpdate.PhotoHash = tempnode.GetValue(nsVcardUpdate, "photo")
			logVerbose("PhotoHash In Presence Update: %s", tempUpdate.PhotoHash)
		}
		updates = append(updates, tempUpdate)
	}

	return updates, nil
}
Exemple #15
0
func main() {
	//
	// Parse XML
	//
	doc := xmlx.New()
	if err := doc.LoadFile("note.xml"); err != nil {
		fmt.Printf("Error: %v\n", err)
	} else {
		notes := doc.SelectNodes("", "note")
		for i, note := range notes {
			if note != nil {
				fmt.Printf("Note #%v: %v\n", i, note)
			}
		}
		fmt.Printf("\n")

		from := doc.SelectNodes("", "from")
		to := doc.SelectNodes("", "to")
		bodies := doc.SelectNodes("", "body")
		for i, _ := range bodies {
			fmt.Printf("From %v to %v: %v\n", from[i], to[i], bodies[i])
		}
	}
}
Exemple #16
0
func getJID(msg string) (string, os.Error) {
	xmlDoc := xmlx.New()

	if err := xmlDoc.LoadString(msg); err != nil {
		logError(err)
		return "", err
	}

	/*
		<iq type='result' id='bind_2'>
		  <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
			<jid>[email protected]/someresource</jid>
		  </bind>
		</iq>
	*/
	node := xmlDoc.SelectNode(nsBind, "jid")
	if node != nil {
		logVerbose("jid value: %s", node.Value)
		return node.Value, nil
	}

	logVerbose("jid value not found")
	return "", os.NewError(fmt.Sprintf("No JID found in: %s", msg))
}
Exemple #17
0
func getAvatars(msg string) ([]AvatarUpdate, os.Error) {
	xmlDoc := xmlx.New()
	var updates []AvatarUpdate
	var tempUpdate AvatarUpdate

	if err := xmlDoc.LoadString(msg); err != nil {
		logError(err)
		return nil, err
	}

	/* VCARD
	<iq from='*****@*****.**' to='[email protected]/orchard' type='result' id='vc2'>
		<vCard xmlns='vcard-temp'>
			<BDAY>1476-06-09</BDAY>
			<ADR>
				<CTRY>Italy</CTRY>
				<LOCALITY>Verona</LOCALITY>
				<HOME/>
			</ADR>
			<NICKNAME/>
			<N>
				<GIVEN>Juliet</GIVEN>
				<FAMILY>Capulet</FAMILY>
			</N>
			<EMAIL>[email protected]</EMAIL>
			<PHOTO>
				<TYPE>image/jpeg</TYPE>
				<BINVAL>
				Base64-encoded-avatar-file-here!
				</BINVAL>
			</PHOTO>
		</vCard>
	</iq>
	*/

	fromjid := ""
	iqnodes := xmlDoc.SelectNodes("", "iq")
	for _, iqnode := range iqnodes {
		fromjid = iqnode.GetAttr("", "from")
		logVerbose("photo from: %s", fromjid)

		node := iqnode.SelectNode(nsVcard, "PHOTO")
		if node != nil {
			phototype := node.GetValue(nsVcard, "TYPE")
			logVerbose("photo type: %s", phototype)

			base64pic := node.GetValue(nsVcard, "BINVAL")
			if base64pic != "" {
				//base64 has \r\n legal, but xml can strip off the \r
				//see http://lists.w3.org/Archives/Public/w3c-ietf-xmldsig/2001AprJun/0188.html
				//safer to just remove \n (0xa) altogether, or maybe replace it with (0xda)
				base64pic = strings.Replace(base64pic, "\n", "", -1)
				dbuf := make([]byte, base64.StdEncoding.DecodedLen(len(base64pic)))
				if _, err := base64.StdEncoding.Decode(dbuf, []byte(base64pic)); err != nil {
					logError(err)
					return updates, err
				}
				tempUpdate.JID = fromjid
				tempUpdate.Photo = dbuf
				tempUpdate.Type = phototype
				updates = append(updates, tempUpdate)
			}
		}
	}

	return updates, nil
}
Exemple #18
0
/**************************************************************
 * INTERNAL - Parsing
 **************************************************************/
func getMessageType(msg string) (int, os.Error) {
	xmlDoc := xmlx.New()
	if err := xmlDoc.LoadString(msg); err != nil {
		logError(err)
		return Error, err
	}

	logVerbose("Root:%s, ns:%s", xmlDoc.Root.Name.Local, xmlDoc.Root.Name.Space)
	//logVerbose("Child:%s, ns:%s", xmlDoc.Root.Children[0].Name.Local, xmlDoc.Root.Children[0].Name.Space)

	/*
		<presence xml:lang='en'>
		  <show>dnd</show>
		  <status>Wooing Juliet</status>
		  <status xml:lang='cz'>Ja dvo&#x0159;&#x00ED;m Juliet</status>
		  <priority>1</priority>
		</presence>
	*/
	node := xmlDoc.SelectNode("", "presence")
	if node != nil {
		logVerbose("GetMessageType:Presence")
		return Presence, nil
	}
	/*
		<message
			to='[email protected]/orchard'
			from='[email protected]/balcony'
			type='chat'
			xml:lang='en'>
		  <body>Art thou not Romeo, and a Montague?</body>
		  <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread>
		</message>
	*/
	node = xmlDoc.SelectNode("", "message")
	if node != nil {
		logVerbose("GetMessageType:Message")
		return Message, nil
	}

	node = xmlDoc.SelectNode("", "iq")
	if node != nil {
		logVerbose("GetMessageType:IQ, looking for specifics")

		/* google chat:
		<iq from="gmail.com" type="result" id="sess_1"/>
		*/
		if strings.Contains(node.GetAttr("", "id"), "sess") {
			logVerbose("GetMessageType:Session, google style")
			return Session, nil
		}

		/* facebook:
		<iq type="result" from="chat.facebook.com" id="sess_1">
			<session xmlns="urn:ietf:params:xml:ns:xmpp-session"/>
		</iq>"
		*/
		node = xmlDoc.SelectNode(nsSession, "session")
		if node != nil {
			logVerbose("GetMessageType:Session")
			return Session, nil
		}

		/* VCARD
		<iq from='*****@*****.**' to='[email protected]/orchard' type='result' id='vc2'>
			<vCard xmlns='vcard-temp'>
				<BDAY>1476-06-09</BDAY>
				<ADR>
					<CTRY>Italy</CTRY>
					<LOCALITY>Verona</LOCALITY>
					<HOME/>
				</ADR>
				<NICKNAME/>
				<N>
					<GIVEN>Juliet</GIVEN>
					<FAMILY>Capulet</FAMILY>
				</N>
				<EMAIL>[email protected]</EMAIL>
				<PHOTO>
					<TYPE>image/jpeg</TYPE>
					<BINVAL>
					Base64-encoded-avatar-file-here!
					</BINVAL>
				</PHOTO>
			</vCard>
		</iq>
		*/
		node = xmlDoc.SelectNode(nsVcard, "vCard")
		if node != nil {
			logVerbose("GetMessageType:VCard")
			return VCard, nil
		}

		/* BIND/JID
		<iq type='result' id='bind_2'>
		  <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
			<jid>[email protected]/someresource</jid>
		  </bind>
		</iq>
		*/
		node = xmlDoc.SelectNode(nsBind, "jid")
		if node != nil {
			logVerbose("GetMessageType:JID")
			return JID, nil
		}
	}

	/*
		<iq to='[email protected]/balcony' type='result' id='roster_1'>
		  <query xmlns='jabber:iq:roster'>
			<item jid='*****@*****.**'
			      name='Romeo'
			      subscription='both'>
			  <group>Friends</group>
			</item>
		  </query>
		</iq>
	*/
	node = xmlDoc.SelectNode(nsRoster, "query")
	if node != nil {
		logVerbose("GetMessageType:Roster")
		return Roster, nil
	}

	/*
		<stream:features>
		  <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>
			<required/>
		  </starttls>
		  <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
			<mechanism>DIGEST-MD5</mechanism>
			<mechanism>PLAIN</mechanism>
		  </mechanisms>
		</stream:features>
	*/
	node = xmlDoc.SelectNode(nsStream, "features")
	if node != nil {
		logVerbose("GetMessageType:features")
		return Features, nil
	}
	node = xmlDoc.SelectNode("stream", "features")
	if node != nil {
		logVerbose("GetMessageType:features")
		return Features, nil
	}

	/*
		<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
		cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgi
		LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==
		</challenge>
	*/
	node = xmlDoc.SelectNode(nsSASL, "challenge")
	if node != nil {
		logVerbose("GetMessageType:challenge")
		return Challenge, nil
	}

	/*
		<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
	*/
	node = xmlDoc.SelectNode(nsSASL, "success")
	if node != nil {
		logVerbose("GetMessageType:success")
		return Success, nil
	}

	/*
		<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
		  <incorrect-encoding/>
		</failure>
	*/
	node = xmlDoc.SelectNode(nsSASL, "failure")
	if node != nil {
		logVerbose("GetMessageType:Failure")
		return SASLFailure, nil
	}

	/*
		<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
	*/
	node = xmlDoc.SelectNode(nsTLS, "proceed")
	if node != nil {
		logVerbose("GetMessageType:proceed")
		return Proceed, nil
	}

	logVerbose("GetMessageType:unknown")
	return Unknown, nil
}
Exemple #19
0
/**
 *  for challenge type, determine appropriate response
 * 	responding to an SASL challenge: http://www.ietf.org/rfc/rfc2831.txt
 **/
func getChallengeResp_DIGESTMD5(challenge string, username string, password string, cnonce string, forceRealm string) (string, os.Error) {
	keyValueMap := make(map[string]string)
	xmlDoc := xmlx.New()

	if err := xmlDoc.LoadString(challenge); err != nil {
		logError(err)
		return "", err
	}

	node := xmlDoc.SelectNode(nsSASL, "challenge")
	if node == nil {
		return "", os.NewError(fmt.Sprintf("No Challenge in: ", challenge))
	}
	logVerbose("urn:ietf:params:xml:ns:xmpp-sasl,challenge Node found")
	logVerbose("challenge: %s", node.Value)

	/*
		<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
		cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgi
		LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==
		</challenge>
		is base64 encoded to begin with based on IMAP4 AUTHENTICATE command [RFC 2060],

		decodes to something like

		digest-challenge  =
			1#( realm | nonce | qop-options | stale | maxbuf | charset
			    algorithm | cipher-opts | auth-param )
			ex:
			realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
			algorithm=md5-sess,charset=utf-8
	*/

	dbuf := make([]byte, base64.StdEncoding.DecodedLen(len(node.Value)))
	if _, err := base64.StdEncoding.Decode(dbuf, []byte(node.Value)); err != nil {
		logVerbose("Error Decoding.")
	}
	logVerbose("Decoded: %s", dbuf)

	//tokenize challenge properties, and store in map for convenience
	//some of them will be reused to send the response
	tokens := strings.Split(string(dbuf), ",", -1)
	for _, tok := range tokens {
		logVerbose("token: " + tok)
		pair := strings.Split(tok, "=", 2)
		logVerbose(pair[0] + ":" + pair[1])
		keyValueMap[pair[0]] = strings.Trim(pair[1], "'\"")
	}

	/*
		digest-response  =
			1#( username | realm | nonce | cnonce |
				nonce-count | qop | digest-uri | response |
				maxbuf | charset | cipher | authzid |
				auth-param )
			ex:
				charset=utf-8,username="******",realm="elwood.innosoft.com",
				nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk",
				digest-uri="imap/elwood.innosoft.com",
				response=d388dad90d4bbd760a152321f2143af7,qop=auth
	*/

	/*
		from the digest response above, 'response' is complicated

		from RFC2831:

		Let H(s) be the 16 octet MD5 hash [RFC 1321] of the octet string s.

		Let KD(k, s) be H({k, ":", s}), i.e., the 16 octet hash of the string
		k, a colon and the string s.

		Let HEX(n) be the representation of the 16 octet MD5 hash n as a
		string of 32 hex digits (with alphabetic characters always in lower
		case, since MD5 is case sensitive).

		response-value  =	HEX(
								KD (
									  HEX(H(A1)), { nonce-value, ":" nc-value, ":", cnonce-value, ":", qop-value, ":", HEX(H(A2)) }
								   )
							   )

		A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
			 ":", nonce-value, ":", cnonce-value, ":", authzid-value }
		A2       = { "AUTHENTICATE:", digest-uri-value }
	*/

	var realm string

	if forceRealm != "" {
		realm = forceRealm
	} else {
		realm = keyValueMap["realm"]
	}

	/* HEX(H(A1)) */
	hash := md5.New()
	hash.Write([]byte(username + ":" + realm + ":" + password))
	X := hash.Sum()
	A1 := append(X, []byte(":"+keyValueMap["nonce"]+":"+cnonce)...)
	hash.Reset()
	hash.Write(A1)
	HA1 := hash.Sum()
	HEXHA1 := strings.ToLower(fmt.Sprintf("%x", HA1))
	logVerbose("HEXHA1: %s", HEXHA1)

	/* HEX(H(A2)) */
	digesturi := "xmpp/" + realm
	A2 := "AUTHENTICATE:" + digesturi
	hash.Reset()
	hash.Write([]byte(A2))
	HA2 := string(hash.Sum())
	HEXHA2 := strings.ToLower(fmt.Sprintf("%x", HA2))
	logVerbose("HEXHA2: %s", HEXHA2)

	hash.Reset()
	hash.Write([]byte(HEXHA1 + ":" + keyValueMap["nonce"] + ":" + noncecount + ":" + cnonce + ":" + keyValueMap["qop"] + ":" + HEXHA2))
	KD := string(hash.Sum())
	HEXKD := strings.ToLower(fmt.Sprintf("%x", KD))
	logVerbose("HEXKD: %s", HEXKD)

	reply := "username='******'" +
		",realm='" + realm + "'" +
		",nonce='" + keyValueMap["nonce"] + "'" +
		",cnonce='" + cnonce + "'" +
		",nc=" + noncecount +
		",qop=" + keyValueMap["qop"] +
		",digest-uri='" + digesturi + "'" +
		",response=" + HEXKD +
		",charset=" + keyValueMap["charset"]
	//",authzid='" + authzid + "'" //authzid in RFC2222
	reply = strings.Replace(reply, "'", "\"", -1)

	//is base64 encoded to begin with based on IMAP4 AUTHENTICATE command [RFC 2060],
	logVerbose("formed reply: %s", reply)
	base64buf := make([]byte, base64.StdEncoding.EncodedLen(len(reply)))
	base64.StdEncoding.Encode(base64buf, []byte(reply))

	return "<response xmlns='" + nsSASL + "'>" + string(base64buf) + "</response>", nil
}