// This layer of the XMPP protocol reads XMLish structures and// responds to them. It negotiates TLS and authentication.packagexmppimport("encoding/xml""fmt""log")// Callback to handle a stanza with a particular id.typecallbackstruct{idstringffunc(Stanza)}// Receive XMPP stanzas from the client and send them on to the// remote. Don't allow the client to send us any stanzas until// negotiation has completed. This loop is paused until resource// binding is complete. Otherwise the app might inject something// inappropriate into our negotiations with the server. The control// channel controls this loop's activity.funcsendStream(sendXmlchan<-interface{},recvXmpp<-chanStanza,status<-chanStatus){deferclose(sendXml)varinput<-chanStanzafor{select{casestat,ok:=<-status:if!ok{return}switchstat{default:input=nilcaseStatusRunning:input=recvXmpp}casex,ok:=<-input:if!ok{return}ifx==nil{ifDebug{log.Println("Won't send nil stanza")}continue}sendXml<-x}}}// Receive XMLish structures, handle all the stream-related ones, and// send XMPP stanzas on to the client once the connection is running.func(cl*Client)recvStream(recvXml<-chaninterface{},sendXmppchan<-Stanza,status<-chanStatus){deferclose(sendXmpp)defercl.statmgr.close()handlers:=make(map[string]func(Stanza))doSend:=falsefor{select{casestat:=<-status:switchstat{default:doSend=falsecaseStatusRunning:doSend=true}caseh:=<-cl.handlers:handlers[h.id]=h.fcasex,ok:=<-recvXml:if!ok{return}switchobj:=x.(type){case*stream:// Do nothing.case*streamError:cl.setError(fmt.Errorf("%#v",obj))returncase*Features:cl.handleFeatures(obj)case*starttls:cl.handleTls(obj)case*auth:cl.handleSasl(obj)caseStanza:id:=obj.GetHeader().Idifhandlers[id]!=nil{f:=handlers[id]delete(handlers,id)f(obj)}ifdoSend{sendXmpp<-obj}default:ifDebug{log.Printf("Unrecognized input: %T %#v",x,x)}}}}}func(cl*Client)handleFeatures(fe*Features){cl.Features=feiffe.Starttls!=nil{start:=&starttls{XMLName:xml.Name{Space:NsTLS,Local:"starttls"}}cl.sendRaw<-startreturn}iflen(fe.Mechanisms.Mechanism)>0{cl.chooseSasl(fe)return}iffe.Bind!=nil{cl.bind()return}}func(cl*Client)handleTls(t*starttls){cl.layer1.startTls(&cl.tlsConfig)cl.setStatus(StatusConnectedTls)// Now re-send the initial handshake message to start the new// session.cl.sendRaw<-&stream{To:cl.Jid.Domain,Version:XMPPVersion}}// Send a request to bind a resource. RFC 3920, section 7.func(cl*Client)bind(){res:=cl.Jid.ResourcebindReq:=&bindIq{}ifres!=""{bindReq.Resource=&res}msg:=&Iq{Header:Header{Type:"set",Id:NextId(),Nested:[]interface{}{bindReq}}}f:=func(stStanza){iq,ok:=st.(*Iq)if!ok{cl.setError(fmt.Errorf("non-iq response to bind %#v",st))return}ifiq.Type=="error"{cl.setError(fmt.Errorf("Resource binding failed"))return}varbindRepl*bindIqfor_,ele:=rangeiq.Nested{ifb,ok:=ele.(*bindIq);ok{bindRepl=bbreak}}ifbindRepl==nil{cl.setError(fmt.Errorf("Bad bind reply: %#v",iq))return}jidStr:=bindRepl.JidifjidStr==nil||*jidStr==""{cl.setError(fmt.Errorf("empty resource in bind %#v",iq))return}jid:=new(JID)iferr:=jid.Set(*jidStr);err!=nil{cl.setError(fmt.Errorf("bind: an't parse JID %s: %v",*jidStr,err))return}cl.Jid=*jidcl.setStatus(StatusBound)}cl.SetCallback(msg.Id,f)cl.sendRaw<-msg}// Register a callback to handle the next XMPP stanza (iq, message, or// presence) with a given id. The provided function will not be called// more than once. If it returns false, the stanza will not be made// available on the normal Client.Recv channel. The callback must not// read from that channel, as deliveries on it cannot proceed until// the handler returns true or false.func(cl*Client)SetCallback(idstring,ffunc(Stanza)){h:=&callback{id:id,f:f}cl.handlers<-h}