Component trackback

You are here: All Systems / webutils / trackback

;;;; trackback.lisp - standalone trackback ping

(defpackage :webutils.trackback
  (:use :common-lisp :trivial-http :split-sequence :cl-ppcre)
  (:export :ping :autodetect-ping-for-url :ping-xml-rpc)
)

(in-package :webutils.trackback)
(webutils::export-all :webutils.trackback)

#+sbcl
(defmacro with-host-timeout (time &body body)
  `(sb-ext:with-timeout ,time ,@body)
)


#-sbcl
(defmacro with-host-timeout (time &body body)
  (declare (ignore time))
  `(progn ,@body)
)


(defun mini-xml-read (xml-string)
  (let ((cur-xml (list nil))
        (cur-string (copy-seq ""))
        (cur-tag nil)
        (xml-tag-to-close nil)
        (actual-xml-tag nil)
)

    (loop for c across xml-string
       do
         (if (not cur-tag)
             (if (char= c #\<)
                 (setf cur-tag (copy-seq ""))
                 (setf cur-string (concatenate 'string cur-string (string c)))
)

             (if (char= c #\>)
                 (if (string-equal cur-tag
                                   (concatenate 'string "/" (car xml-tag-to-close))
)

                     (progn
                       (let ((wrap (pop cur-xml)))
                         (setf (car cur-xml)
                               (append (car cur-xml)
                                       (list (cons
                                              (pop actual-xml-tag)
                                              (append
                                               wrap
                                               (if (> (length cur-string) 0)
                                                   (list cur-string)
)
)
)
)
)
)
)

                       ;;(format t "cur-xml is now ~S~%" cur-xml)
                      (pop xml-tag-to-close)
                       (setf cur-tag nil)
                       (setf cur-string (copy-seq ""))
)

                     (if (string-equal (car (split-sequence #\space cur-tag))
                                       "?xml"
)

                         (setf cur-tag nil)
                         (progn
                           (setf (car cur-xml)
                                 (append (car cur-xml)
                                         (if (> (length cur-string) 0)
                                             (list cur-string)
)
)
)

                           ;;(format t "cur-xml is now ~S~%" cur-xml)
                          (push nil cur-xml)
                           ;;(format t "cur-xml is now ~S~%" cur-xml)
                          (push cur-tag actual-xml-tag)
                           (push (car (split-sequence #\space cur-tag))
                                 xml-tag-to-close
)

                           (setf cur-tag nil)
                           (setf cur-string (copy-seq ""))
)
)
)

                 (setf cur-tag (concatenate 'string cur-tag (string c)))
)
)
)

    (if (> (length cur-string) 0)
        ;; hopefully everything's closed off at this point
       (setf (car cur-xml)
              (append (car cur-xml)
                      (list cur-string)
)
)
)

    (car cur-xml)
)
)


(defun ping (trackback-url &key title url excerpt blog-name)
  (assert (every #'stringp (list title url excerpt blog-name)))
  (block ping-return
    (handler-case
        (with-host-timeout 5
          (destructuring-bind (response headers stream)
              (http-post trackback-url "application/x-www-form-urlencoded"
                         (format nil "~{url=~A&title=~A&excerpt=~A&blog_name=~A~}"
                                 (mapcar #'escape-url-query
                                         (list url title excerpt blog-name)
)
)
)

            (declare (ignore headers))
            (unwind-protect
                 (if (not (eql response 200))
                     (values nil (format nil "HTTP error ~A" response))
                     (let ((xml (copy-seq ""))
                           (parsed-xml nil)
                           (c (read-char stream))
                           (error nil)
)

                       (loop while c
                          do
                            (progn
                              (setf xml (concatenate 'string xml (string c)))
                              (setf c (read-char stream nil))
)
)

                       (setf parsed-xml (mini-xml-read xml))
                       (mapc #'(lambda (x)
                                 (when (and (consp x) (string-equal (car x) "response"))
                                   (mapc #'(lambda (x)
                                             (if (and (consp x)
                                                      (string-equal (car x) "error")
)

                                                 (setf error
                                                       (not
                                                        (some
                                                         #'(lambda (e)
                                                             (and (stringp e)
                                                                  (eql
                                                                   (parse-integer e :junk-allowed t) 0
)
)
)

                                                         (cdr x)
)
)
)

                                                 (if (and (consp x)
                                                          error
                                                          (string-equal (car x) "message")
)

                                                     (return-from ping-return
                                                       (values nil (cdr x))
)
)
)
)

                                         (cdr x)
)
)
)
parsed-xml
)

                       (if error
                           (values nil "Unknown error")
                           t
)
)
)

              (if stream (close stream))
)
)
)

      (serious-condition () (return-from ping nil))
)
)
)


(defmacro aif2 (test-form if-form &optional else-form)
  (let ((test-val (gensym)))
    `(multiple-value-bind (,test-val it) ,test-form
       (if ,test-val
           ,if-form
           ,@(if else-form (list else-form))
)
)
)
)


(defun autodetect-ping-for-url (url)
  (let ((best-guess nil))
    (handler-case
        (with-host-timeout 2
          (destructuring-bind (response headers stream) (http-get url)
            (declare (ignore headers))
            (unwind-protect
                 (if (not (eql response 200))
                     (return-from autodetect-ping-for-url nil)
                     (progn
                       (loop for line = (read-line stream nil nil)
                          with in-rdf = nil
                          with found = nil
                          while line
                          do (progn
                               (if (scan "(?i)<rdf:description" line)
                                   (setf in-rdf t)
)

                               (aif2 (scan-to-strings "(?i)rdf:about=\"(.+)\"" line)
                                     (if (string= (elt it 0) url)
                                         (setf found t)
)
)

                               (aif2 (scan-to-strings "(?i)dc:identifier=\"(.+)\"" line)
                                     (if (string= (elt it 0) url)
                                         (setf found t)
)
)

                               (aif2 (scan-to-strings "(?i)trackback:ping=\"(.+)\"" line)
                                     (if found
                                         (return-from autodetect-ping-for-url (elt it 0))
                                         (setf best-guess (elt it 0))
)
)

                               (when (scan "(?i)</rdf:rdf>" line)
                                 (setf in-rdf nil)
                                 (setf found nil)
)
)

                          finally (return best-guess)
)
)
)
)
)
)

      (serious-condition () (return-from autodetect-ping-for-url best-guess))
)
)
)


(defun ping-xml-rpc (ping-url site-name site-url)
  (handler-case
      (with-host-timeout 5
        (destructuring-bind (response headers stream)
            (http-post ping-url "text/xml"
                       (format nil "
<?xml version=
\"1.0\"?>
<methodCall>
<methodName>weblogUpdates.ping</methodName>
<params>
<param>
<value>~A</value>
</param>
<param>
<value>~A</value>
</param>
</params>
</methodCall>"

site-name site-url
)
)

          (declare (ignore headers))
          (unwind-protect
               (if (not (eql response 200))
                   (values nil (format nil "HTTP error ~A" response))
                   (let ((xml (copy-seq ""))
                         (parsed-xml nil)
                         (c (read-char stream))
)

                     (loop while c
                           do
                           (progn
                             (setf xml (concatenate 'string xml (string c)))
                             (setf c (read-char stream nil))
)
)

                     (setf parsed-xml (mini-xml-read xml))
)
)

            (if stream (close stream))
)
)
)

    (serious-condition () (return-from ping-xml-rpc nil))
)
)

Lisppaste pastes can be made by anyone at any time. Imagine a fearsomely comprehensive disclaimer of liability. Now fear, comprehensively.