2020-06-25 15:55:37 +00:00
( in-package :whatsxmpp )
( defun make-message-uuid ( comp )
( with-accessors ( ( promises component-promises ) ) comp
( let ( ( uuid ( string-downcase ( write-to-string ( uuid:make-v4-uuid ) ) ) )
( promise ( make-promise ) ) )
( setf ( gethash uuid promises ) promise )
( values uuid promise ) ) ) )
( defmacro with-stanza ( ( comp stanza-name &key type from to id ) &body body )
( alexandria:with-gensyms ( uuid ret from-sym id-sym )
` ( with-component-xml-output ( , comp )
( let ( ( , from-sym ( or , from ( component-name , comp ) ) )
( , id-sym , id ) )
( multiple-value-bind ( , uuid , ret )
( if , id-sym
( values , id-sym , id-sym )
( make-message-uuid , comp ) )
( cxml:with-element , stanza-name
( cxml:attribute "from" , from-sym )
( cxml:attribute "id" , uuid )
, ( when to
` ( cxml:attribute "to" , to ) )
, ( when type
` ( cxml:attribute "type" , type ) )
,@ body )
, ret ) ) ) ) )
( defmacro with-iq ( ( comp to &key ( type "get" ) from id ) &body body )
"Send an IQ stanza (of type TYPE) on the COMP component, from the JID FROM (default: component name) to the JID TO, with BODY specifying further CXML commands to make up the body of the stanza. Returns a promise."
` ( with-stanza ( , comp "iq"
:type , type
:to , to
:from , from
:id , id )
,@ body ) )
( defmacro with-message ( ( comp to &key ( type "chat" ) from id ) &body body )
"Send a message stanza (of type TYPE) on the COMP component. Semantics the same as WITH-IQ, except for the fact that message stanzas don't normally prompt a response."
` ( with-stanza ( , comp "message"
:type , type
:to , to
:from , from
:id , id )
,@ body ) )
( defmacro with-presence ( ( comp to &key type from id ) &body body )
"Send a presence stanza (of type TYPE) on the COMP component. Semantics the same as WITH-IQ, except for the fact that presence stanzas don't normally prompt a response."
` ( with-stanza ( , comp "presence"
:type , type
:to , to
:from , from
:id , id )
,@ body ) )
( defun get-node-named ( nodes name )
"Finds the node with tag name NAME in NODES, returning NIL if none was found."
2020-07-31 14:45:07 +00:00
( flet ( ( is-the-node ( node ) ( and ( dom:element-p node ) ( equal ( dom:tag-name node ) name ) ) ) )
2020-06-25 15:55:37 +00:00
( find-if #' is-the-node nodes ) ) )
( defun get-node-with-xmlns ( nodes xmlns )
"Finds the node with XML namespace XMLNS in NODES, returning NIL if none was found."
2020-07-31 14:45:07 +00:00
( flet ( ( is-the-node ( node ) ( and ( dom:element-p node ) ( equal ( dom:get-attribute node "xmlns" ) xmlns ) ) ) )
2020-06-25 15:55:37 +00:00
( find-if #' is-the-node nodes ) ) )
2020-09-01 19:01:02 +00:00
( defun get-node-text ( node )
"Gets the node's text."
( let ( ( child-nodes ( dom:child-nodes node ) ) )
( if ( > ( length child-nodes ) 0 )
( dom:node-value ( elt child-nodes 0 ) )
"" ) ) )
2020-06-25 15:55:37 +00:00
( defun handle-stream-error ( comp stanza )
( flet ( ( is-error-node ( node )
( equal ( dom:namespace-uri node ) +streams-ns+ ) )
( is-text-node ( node )
( equal ( dom:tag-name node ) "text" ) ) )
2020-07-31 14:45:07 +00:00
( let* ( ( children ( child-elements stanza ) )
2020-06-25 15:55:37 +00:00
( error-node ( find-if #' is-error-node children ) )
( error-text-node ( find-if #' is-text-node children ) )
( error-name ( dom:tag-name error-node ) )
( error-text ( when error-text-node
( dom:node-value ( elt ( dom:child-nodes error-text-node ) 0 ) ) ) ) )
( warn "Stream error of type ~A encountered: ~A" error-name error-text )
( emit :stream-error comp error-name error-text stanza ) ) ) )
( define-condition stanza-error ( error )
( ( defined-condition
:initarg :defined-condition
:accessor stanza-error-condition )
( type
:initarg :type
:accessor stanza-error-type )
( text
:initarg :text
:initform nil
:accessor stanza-error-text )
( raw
:initarg :raw
:initform nil
:accessor stanza-error-raw ) )
( :report ( lambda ( err stream )
( with-slots ( defined-condition type text ) err
( format stream "~A (type ~A): ~A" defined-condition type text ) ) ) ) )
( defun extract-stanza-error ( stanza )
"Extracts a STANZA-ERROR from the given STANZA, which must contain an <error/> element conforming to RFC 6120 § 8.3."
( flet ( ( is-error-condition-node ( node )
( equal ( dom:namespace-uri node ) +stanzas-ns+ ) )
( is-error-node ( node )
( equal ( dom:tag-name node ) "error" ) )
( is-text-node ( node )
( and ( equal ( dom:namespace-uri node ) +stanzas-ns+ ) ( equal ( dom:tag-name node ) "text" ) ) ) )
2020-07-31 14:45:07 +00:00
( let* ( ( error-node ( find-if #' is-error-node ( child-elements stanza ) ) )
( error-children ( child-elements error-node ) )
2020-06-25 15:55:37 +00:00
( type ( dom:get-attribute error-node "type" ) )
( condition-node ( find-if #' is-error-condition-node error-children ) )
( condition-name ( dom:tag-name condition-node ) )
( text-node ( find-if #' is-text-node error-children ) )
( text ( when text-node
( dom:node-value ( elt ( dom:child-nodes text-node ) 0 ) ) ) ) )
( make-condition 'stanza-error
:raw error-node
:defined-condition condition-name
:type type
:text text ) ) ) )
( defun send-stanza-error ( comp &key id to from e stanza-type )
"Send E (a STANZA-ERROR) as an error response to a stanza of type STANZA."
( with-component-xml-output ( comp )
( cxml:with-element stanza-type
( cxml:attribute "type" "error" )
( cxml:attribute "id" id )
( cxml:attribute "from" from )
( cxml:attribute "to" to )
( cxml:with-element "error"
( cxml:attribute "type" ( stanza-error-type e ) )
( cxml:with-element ( stanza-error-condition e )
( cxml:attribute "xmlns" +stanzas-ns+ ) )
( when ( stanza-error-text e )
( cxml:with-element "text"
( cxml:text ( stanza-error-text e ) ) ) ) ) ) ) )
( defun parse-jid ( jid )
"Parse JID, returning the multiple values HOSTNAME, LOCALPART and RESOURCE."
( declare ( type string jid ) )
( let ( ( at-pos ( position #\@ jid ) )
( slash-pos ( position #\/ jid ) ) )
( cond
( ( and ( not slash-pos ) ( not at-pos ) )
( values jid nil nil ) )
( ( and slash-pos ( not at-pos ) )
( multiple-value-bind ( hostname resource )
( whatscl::split-at jid slash-pos )
( values hostname nil resource ) ) )
( ( and ( not slash-pos ) at-pos )
( multiple-value-bind ( localpart hostname )
( whatscl::split-at jid at-pos )
( values hostname localpart nil ) ) )
( t
( multiple-value-bind ( rest resource )
( whatscl::split-at jid slash-pos )
( multiple-value-bind ( localpart hostname )
( whatscl::split-at rest at-pos )
( values hostname localpart resource ) ) ) ) ) ) )
( defun strip-resource ( jid )
"Strips a resource from JID, if there is one, returning the bare JID."
( let ( ( slash-pos ( position #\/ jid ) ) )
( if slash-pos
( whatscl::split-at jid slash-pos )
jid ) ) )