cl-ipp

Internet Printing Protocol (IPP) for Common Lisp
git clone https://logand.com/git/cl-ipp.git/
Log | Files | Refs

commit 127b2e86a6e7325c899760e996f3d81391e6ee73
parent e4d1c94177fea508d6388f1fbb3dcd69864389d5
Author: Tomas Hlavaty <tom@logand.com>
Date:   Wed,  7 Aug 2013 01:30:48 +0200

print-job works via wget

Diffstat:
Mipp.lisp | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
1 file changed, 206 insertions(+), 76 deletions(-)

diff --git a/ipp.lisp b/ipp.lisp @@ -25,47 +25,62 @@ (in-package :ipp) -(defun read-octet (stream) - (if (functionp stream) - (funcall stream 'read-octet) - (read-byte stream))) - -(defun copy-stream (in out) - (handler-case (loop (write-byte (read-octet in) out)) - (end-of-file ()))) - -(defun read-ushort (stream) - (logior (ash (read-octet stream) 8) - (read-octet stream))) - -(defun read-dword (stream) - (logior (ash (read-octet stream) 24) - (ash (read-octet stream) 16) - (ash (read-octet stream) 8) - (read-octet stream))) - -(defun read-octets (stream n) +(defvar *input-stream*) +(defvar *output-stream*) +(defvar *ipp-client*) + +(defun read-octet () + (let ((s *input-stream*)) + (etypecase s + (stream (read-byte s)) + (function (let ((x (funcall s))) + (assert (<= 0 x 256)) + x))))) + +(defun write-octet (x) + (assert (<= 0 x #. (1- (expt 2 8)))) + (let ((s *output-stream*)) + (etypecase s + (stream (write-byte x s)) + (function (funcall s x))))) + +(defun read-ushort () + (logior (ash (read-octet) 8) (read-octet))) + +(defun write-ushort (x) + (assert (<= 0 x #.(1- (expt 2 16)))) + (write-octet (ash x -8)) + (write-octet (logand #xff x))) + +(defun read-dword () + (logior (ash (read-ushort) 16) (read-ushort))) + +(defun write-dword (x) + (assert (<= 0 x #.(1- (expt 2 32)))) + (write-ushort (ash x -16)) + (write-ushort (logand #xffff x))) + +(defun copy-stream () + (when *input-stream* + (handler-case (loop (write-octet (read-octet))) + (end-of-file ())))) + +(defun read-octets (n) (let ((x (make-array n :element-type '(unsigned-byte 8) :initial-element 0))) - (if (functionp stream) - (let ((i 0)) - (handler-case (do () - ((<= n i)) - (setf (aref x i) (read-octet stream)) - (incf i)) - (end-of-file () i))) - (read-sequence x stream)) + (read-sequence x *input-stream*) x)) -(defun make-data-stream (x) +(defun write-octets (x) (etypecase x - (stream (lambda () (read-byte x nil nil))) - (list (lambda () (pop x))) - (vector (let ((n (length x)) - (i 0)) - (lambda () - (when (< i n) - (prog1 (aref x i) - (incf i)))))))) + (stream (let ((*input-stream* x)) (copy-stream))) + (list (mapc 'write-octet x)) + (vector (map 'vector 'write-octet x)))) + +(defun string-to-octets (x) ;; TODO encoding + (ccl:encode-string-to-octets x)) + +(defun octets-to-string (x) ;; TODO encoding + (ccl:decode-string-from-octets x)) (defun tag (x) (let ((tags '((#x01 . :operation-attributes-tag) @@ -102,14 +117,14 @@ (defun attribute-tag (attribute) (cdr (assoc attribute '((:attributes-charset . :charset) - (:attributes-natural-language . nil) + (:attributes-natural-language . :naturalLanguage) (:printer-uri . :uri) - (:requesting-user-name . nil) - (:job-name . nil) - (:ipp-attribute-fidelity . nil) + (:requesting-user-name . :nameWithoutLanguage) + (:job-name . :nameWithoutLanguage) + (:ipp-attribute-fidelity . :boolean) (:document-name . nil) (:document-format . nil) - (:document-natural-language . nil) + (:document-natural-language . :naturalLanguage) (:compression . nil) (:job-k-octets . nil) (:job-impressions . nil) @@ -122,20 +137,56 @@ ;;(attribute-name :attributes-charset) -(defun write-group (group control-stream) +(defun read-attribute () + (let ((tag (read-octet))) + (if (member tag (mapcar 'tag ;; TODO optimize + '(:operation-attributes-tag + :job-attributes-tag + :end-of-attributes-tag + :printer-attributes-tag + :unsupported-attributes-tag))) + tag + `(,tag ;;(tag (attribute-tag k)) + ,(octets-to-string (read-octets (read-ushort))) + , (case tag ;; TODO handle more cases + ((33 35) + (assert (= 4 (read-ushort))) + (read-dword)) + (t (octets-to-string (read-octets (read-ushort))))))))) + +(defun read-groups () + (let ((sentinel (tag :end-of-attributes-tag)) ;; TODO optimize + (x (read-octet))) + (loop + until (= sentinel x) + collect `(,x + ,@(loop + while (consp (setq x (read-attribute))) + collect x))))) + +#+nil +(with-open-file (*input-stream* "response.dat" :element-type '(unsigned-byte 8)) + (read-ipp 314)) + +(defun write-group (group) (destructuring-bind (group-id &rest plist) group - (when plist ;; TODO exists not null v in plist - (write-octet group-id control-stream) + (when (loop + for (k v) on plist by #'cddr + when v + do (return t)) + (write-octet group-id) (loop for (k v) on plist by #'cddr when v do (let ((%k (string-to-octets (attribute-name k))) (%v (string-to-octets v))) - (write-octet (tag (attribute-tag k)) control-stream) - (write-octet (length %k) control-stream) - (write-octets %k control-stream) - (write-ushort (length %v) control-stream) - (write-octets %v control-stream)))))) + ;; TODO additional value (when v is list) + (write-octet (tag (attribute-tag k))) + (write-ushort (length %k)) + (write-octets %k) + (write-ushort (length %v)) + (write-octets %v)))))) + (defun operation-code (operation) (cdr (assoc operation '((:print-job 1 0 #x0002) @@ -181,33 +232,47 @@ ;;(operation-code :print-job) -(defun write-ipp (control-stream request-id operation groups data-stream) +(defun write-ipp (data-file request-id operation groups) (destructuring-bind (major minor code) (operation-code operation) - (write-octet major control-stream) - (write-octet minor control-stream) - (write-ushort code control-stream) - (write-dword request-id control-stream) - (dolist (group groups) - (write-group group control-stream)) - (write-octet (tag :end-of-attributes-tag) control-stream) - (when data-stream - (copy-stream data-stream control-stream)))) - -(defun read-ipp (control-stream) + (write-octet major) + (write-octet minor) + (write-ushort code) + (write-dword request-id) + (mapc 'write-group groups) + (write-octet (tag :end-of-attributes-tag)) + (when data-file + (with-open-file (*input-stream* data-file :element-type '(unsigned-byte 8)) + (copy-stream))))) + +(defun read-ipp (request-id) `(:ipp-response - :major ,(read-octet control-stream) - :minor ,(read-octet control-stream) - :code ,(read-ushort control-stream) - :request-id ,(read-dword control-stream) - :groups ,(read-groups control-stream))) + :major ,(read-octet) + :minor ,(read-octet) + :code ,(read-ushort) + :request-id , (let ((x (read-dword))) + (assert (= x request-id)) + x) + :groups ,(read-groups))) -(defun ipp (control-stream request-id operation groups data-stream) - (write-ipp control-stream request-id operation groups data-stream) - (read-ipp control-stream)) +#+nil +(with-open-file (*input-stream* "response.dat" :element-type '(unsigned-byte 8)) + (read-ipp 314)) -(defun print-job (control-stream +(defun ipp (request-file response-file data-file request-id operation groups) + (with-open-file (*output-stream* request-file + :element-type '(unsigned-byte 8) + :direction :output + :if-exists :supersede + :if-does-not-exist :create) + (write-ipp data-file request-id operation groups)) + (funcall *ipp-client* request-file response-file) + (with-open-file (*input-stream* response-file :element-type '(unsigned-byte 8)) + (read-ipp request-id))) + +(defun print-job (request-file + response-file + data-file request-id - data-stream attributes-charset attributes-natural-language printer-uri @@ -222,7 +287,9 @@ job-k-octets job-impressions job-media-sheets) - (ipp control-stream + (ipp request-file + response-file + data-file request-id :print-job `((,(tag :operation-attributes-tag) @@ -239,7 +306,70 @@ :compression ,compression :job-k-octets ,job-k-octets :job-impressions ,job-impressions - :job-media-sheets ,job-media-sheets)) - data-stream)) + :job-media-sheets ,job-media-sheets)))) + +(defpackage :ipp.wget + (:use :cl)) + +(in-package :ipp.wget) + +(defun wget (url &key request-file response-file content-type) + (ccl:run-program + "wget" + `("-q" + ,@ (when request-file + `("--post-file" ,request-file)) + ,@ (when response-file + `("-O" ,response-file)) + ,@ (when content-type + `("--header" ,(format nil "Content-Type:~a" content-type))) + ,url))) + +;;(wget "http://localhost:631/printers/" :response-file "/tmp/a.html") +;; wget|curl|lisp-http-client driver/backend + +(defun make-ipp-client (printer-uri) + (lambda (request-file response-file) + (wget printer-uri + :request-file request-file + :response-file response-file + :content-type "application/ipp"))) + +#+nil +(let* ((url "http://localhost:631/printers/Virtual_PDF_Printer") + (ipp::*ipp-client* (make-ipp-client url))) + (ipp::print-job "request2.dat" "response2.dat" "test.txt" 314 "utf-8" "en" url + :requesting-user-name "tomas")) + +(defpackage :ipp.curl + (:use :cl)) + +(in-package :ipp.curl) + +(defun curl (url &key request-file response-file content-type) + (ccl:run-program + "curl" + `("-s" + ,@ (when request-file + `("-d" ,(format nil "@~a" request-file))) + ,@ (when response-file + `("-o" ,response-file)) + ,@ (when content-type + `("-H" ,(format nil "Content-Type:~a" content-type))) + ,url))) + +;;(curl "http://localhost:631/printers/" :response-file "/tmp/a.html") +;; curl|curl|lisp-http-client driver/backend + +(defun make-ipp-client (printer-uri) + (lambda (request-file response-file) + (curl printer-uri + :request-file request-file + :response-file response-file + :content-type "application/ipp"))) -;;(print-job control-stream 1 data-stream "utf-8" "en_GB" "ipp://localhost:631/printers/myprinter") +#+nil ;; TODO fix Bad Request response +(let* ((url "http://localhost:631/printers/Virtual_PDF_Printer") + (ipp::*ipp-client* (make-ipp-client url))) + (ipp::print-job "request2.dat" "response2.dat" "test.txt" 314 "utf-8" "en" url + :requesting-user-name "tomas"))