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 }
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 }
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() }
// 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) }
// 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") } } }
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) }
/** * 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 }
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 }
// 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 }
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") } } } }
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 }
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]) } } }
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)) }
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 }
/************************************************************** * 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ří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 }
/** * 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 }