You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
5.6 KiB
EmacsLisp

;;; stomp.el --- STOMP protocol library -*- lexical-binding: t -*-
;; Copyright (C) 2018 Hugo Thunnissen
;; Author: Hugo Thunnissen <hugo.thunnissen@gmail.com>
;; Created: 17 October 2018
;; Version: dev-master
;; Keywords: stomp STOMP messaging message
;; Package-Requires: (map)
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
6 years ago
(require 'map)
(define-error 'stomp-error
"Error encountered in implementation of the STOMP protocol")
(define-error 'stomp-invalid-header
"Invalid STOMP frame header encounterd")
(define-error 'stomp-invalid-frame
"Invalid STOMP frame encountered")
(defun stomp-send-frame (process frame)
6 years ago
"Send a message to a STOMP process"
(process-send-string process (stomp-frame-to-string frame)))
6 years ago
(defun stomp-filter-function (callback)
"Filter function to be used with processes"
(unless (functionp callback) (signal 'wrong-type-argument 'functionp))
(lambda (proc string)
(with-current-buffer (process-buffer proc)
(goto-char (point-max))
(insert string)
(let ((frame (stomp-shift-frame (current-buffer))))
(if frame (funcall callback proc frame))))))
6 years ago
(defun stomp-shift-frame (buffer)
"Read a frame from a buffer. After reading a frame, it will
6 years ago
be deleted from the buffer."
(let ((frame (stomp-read-frame buffer)))
6 years ago
(cond
(frame (stomp-delete-frame buffer frame) frame)
6 years ago
(t nil))))
(defun stomp-delete-frame (buffer frame)
6 years ago
"Delete the top-most messsage in a buffer"
(with-current-buffer buffer
(delete-region (point-min) (+ 2 (stomp-find-frame-end-point buffer frame)))))
(defun stomp-frame-header (header frame)
"Shortcut to access frame headers"
(assoc-default
header
(alist-get 'headers frame nil) 'string-equal nil))
6 years ago
(defun stomp-read-frame (buffer)
"Attempt to read a single frame from a buffer.
The buffer may contain multiple frames, but only the first
frame (from top to bottom) will be read. If there are no
frames in the buffer, nil will be returned."
6 years ago
(with-current-buffer buffer
(let ((frame ())
(headers-end-point (stomp-find-headers-end-point buffer)))
(goto-char (point-min))
6 years ago
(cond
((and headers-end-point)
(map-put frame 'command (current-word))
(map-put frame 'headers (stomp-read-headers buffer headers-end-point))
(let ((frame-end-point (stomp-find-frame-end-point buffer frame headers-end-point)))
(if frame-end-point
(map-put frame 'content (buffer-substring (+ 1 headers-end-point) frame-end-point))
nil)))
6 years ago
(t nil)))))
(defun stomp-read-headers (buffer end-point &optional headers)
"Attempt to read the headers of a stomp frame in *buffer*"
6 years ago
(with-current-buffer buffer
(unless headers (goto-char (point-min)))
(forward-line)
(let ((header (split-string (thing-at-point 'line) ":")))
(cond
((= (line-end-position) end-point) headers)
((> (length header) 2) (signal 'stomp-invalid-header (list header)))
(t (stomp-read-headers
buffer
end-point
(map-put headers
(car header)
(replace-regexp-in-string "\\(\n\\|\r\n\\)$" "" (car (last header))))))))))
6 years ago
(defun stomp-find-frame-end-point (buffer frame &optional headers-end-point)
"Find the end point of a STOMP frame at the top of a buffer,
either by using the content-length header or by searching for
the null octet."
6 years ago
(with-current-buffer buffer
(let* ((content-length (stomp-frame-header "content-length" frame))
(headers-end-point
(if headers-end-point headers-end-point (stomp-find-headers-end-point buffer)))
(end-point (if content-length
(+ headers-end-point 1 (string-to-number content-length))
(progn
(goto-char (point-min))
(search-forward-regexp "\u0000\\|\u0000\r" nil t)
(- (point) 1)))))
(if (or (> end-point (point-max)) (<= end-point 0))
nil
end-point))))
6 years ago
(defun stomp-find-headers-end-point (buffer)
"Find the end of the header part of a STOMP frame."
(with-current-buffer buffer
(goto-char (point-min))
(condition-case nil
(progn (search-forward-regexp "^\\(\\|\r\\)$") (line-end-position))
(error nil))))
(defun stomp-frame-to-string (frame)
"Convert a lisp datastructure describing a STOMP frame into a
string that could be sent to a server or client. The
datastructure should be an alist and should at least have a
'command key. Other optional keys are 'headers (another alist)
and 'content"
(unless (assq 'command frame)
(signal 'stomp-invalid-frame (list "Expected frame to have a command" frame)))
(let ((headers (if (assq 'headers frame) (alist-get 'headers frame) '()))
(content (if (assq 'content frame) (alist-get 'content frame) "")))
(format "%s\n%s\n%s\u0000"
(alist-get 'command frame)
(mapconcat (lambda (h) (format "%s:%s\n" (car h) (cdr h))) headers "")
content)))
(provide 'stomp)