(define-module (gnubba xep-0114) #:use-module (gnubba sha1) #:use-module (gnubba sugar) #:use-module (ice-9 rdelim) #:use-module (ice-9 regex) #:use-module (sxml simple) #:export (with-component)) (define component-port 5347) (define (stream-header component-name) (string-concatenate (list ""))) (define (generate-handshake stream-id shared-secret) (sha1 (string-concatenate (list stream-id shared-secret)))) ;; Establish a component connection and execute actions. (define-syntax with-component (syntax-rules () ((with-component domain shared-secret exp ...) (begin (define addr (make-socket-address AF_INET INADDR_LOOPBACK component-port)) (define sock (socket AF_INET SOCK_STREAM 0)) (define (get-attribute name response) (define (get-attributes) "Extract key value pairs from a string and return as alist." (map (lambda (res) (cons (match:substring res 1) (match:substring res 3))) (list-matches "([^= ]+)=(['\"])([^ ]+)\\2" response))) (let ((found (assoc name (get-attributes)))) (unless found (throw 'stream-error 'attribute-not-found name response)) found)) (define (close-stream) ;; TODO: make sure this is understood as "" (display "" sock) ;; TODO: wait for other side to also send a closing tag before terminating the connection (close-port sock)) (define (write-to-stream msg) "Write msg to sock and return a single response tag. Ignore tag." (when msg (display msg sock)) (let ((response-string (read-delimited ">" sock))) (if (string-match "" sock))) response-string) response-string))) (catch #t (lambda () (connect sock addr) (let ((response (write-to-stream (stream-header domain)))) ;; server must reply with either an error or a stream header ;; TODO: should not match against literal string (unless (string-match "xml `(handshake ,(generate-handshake stream-id shared-secret)) sock) (let* ((ns '((stream . "http://etherx.jabber.org/streams"))) (response (xml->sxml sock #:namespaces ns #:trim-whitespace? #t))) ;; expect empty handshake element as acknowlegement (unless (equal? 'handshake (caadr response)) (throw 'stream-error 'handshake response)) (display "Connected to XMPP server!\n") ;; Handlers are passed as expressions that ;; are evaluated in the xmpp monad. (display "Registering handlers...\n") (begin (xmpp-> sock exp ...)) ;; Blocking wait for stanzas. At this ;; point, new handler can only be ;; registered by previously registered ;; handlers. (display "Starting listener loop.\n") (run-handler-loop sock) ;; close connection at the end (display "Closing stream.\n") (close-stream))))) (lambda (key . args) (if (and (equal? 'system-error key) (string-prefix? "connect" (car args))) (begin ;; connection refused or something (display (format #t "ERROR: ~S\n" (cdr args)))) (begin (display (format #t "ERROR: ~S. Closing stream. (~S)\n" key args)) (close-stream)))))))))