cl-rw

Layered streams for Common Lisp
git clone https://logand.com/git/cl-rw.git/
Log | Files | Refs

commit 2cc8e359f7f829feea53aa64fcc5170c3d2bafd6
parent 6585617e506123f0c619f49ba302f2bd7cdf3213
Author: Tomas Hlavaty <tom@logand.com>
Date:   Sun, 22 Sep 2013 18:13:41 +0200

added header parsing, post multipart/form-data handling, some fixes

Diffstat:
Mhttp.lisp | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mrw.lisp | 2+-
Mui.lisp | 16++++++++--------
3 files changed, 95 insertions(+), 27 deletions(-)

diff --git a/http.lisp b/http.lisp @@ -1,3 +1,6 @@ +;; TODO remove all those coerce list<->string? +;; TODO file(s) upload + (defpackage :rw.http (:use :cl) (:export :client @@ -26,19 +29,54 @@ (rw:skip reader)) (prog1 (rw:next-z0 reader) (rw:skip reader)) - (prog1 (coerce (rw:till reader '(#\return #'\newline)) 'string) ;; TODO better + (prog1 (coerce (rw:till reader '(#\return #'\newline)) 'string) (next-eol reader))))) +(defun header-part-reader (reader) + (lambda () + (rw:skip reader) + (when (rw:peek reader) + (flet ((str (y) + (when y + (coerce y 'string)))) + (cons (str (rw:till reader '(#\= #\;))) + (ecase (rw:next reader) + ((nil #\;) nil) + (#\= + (rw:skip reader) + (let ((q (when (eql #\" (rw:peek reader)) + (rw:next reader) + t))) + (prog1 (str (rw:till reader '(#\"))) + (when q + (assert (eql #\" (rw:next reader)))) + (rw:skip reader) + (assert (member (rw:next reader) '(#\; nil)))))))))))) + +(defun parse-header (k v) + (case (cdr (assoc k '(("Content-Length" . :z0) + ("Content-Disposition" . :header-parts) + ("Content-Type" . :header-parts)) + :test #'equal)) + (:z0 (rw:next-z0 (rw:peek-reader (rw:reader v)))) + (:header-parts (rw:till (rw:peek-reader + (header-part-reader + (rw:peek-reader (rw:reader v)))))) + (t (coerce v 'string)))) + +;;(parse-header "Content-Disposition" "form-data; name=\"z7\"; filename=\"\"") +;;(parse-header "Content-Type" "multipart/form-data; boundary=---------------------------333499860151468491119738773") + (defun header-reader (reader) (lambda () (let ((k (rw:till reader '(#\: #\return #\newline)))) (when k - (assert (eql #\: (rw:peek reader))) - (rw:next reader) + (assert (eql #\: (rw:next reader))) (rw:skip reader) - (prog1 (cons (coerce k 'string) ;; TODO better - (coerce (rw:till reader '(#\return #\newline)) 'string)) ;; TODO better - (next-eol reader)))))) + (let* ((kk (coerce k 'string)) + (v (rw:till reader '(#\return #\newline)))) + (next-eol reader) + (cons kk (parse-header kk v))))))) (defun next-headers (reader) (prog1 (rw:till (rw:peek-reader (header-reader reader))) @@ -230,6 +268,39 @@ stream) (write-crlf stream)) +(defun multipart-reader (reader start-boundary end-boundary) + (lambda () + (rw:skip reader) + (when (rw:peek reader) + (let ((boundary (rw:till reader '(#\return #\newline)))) + (unless (equal boundary end-boundary) + (assert (equalp boundary start-boundary)) + (next-eol reader) + (list :part + :headers (next-headers reader) + :body (prog1 (rw:till reader '(#\return #\newline)) + (next-eol reader)))))))) + +(defun next-multipart/form-data (reader boundary) + (rw:till (rw:peek-reader (multipart-reader (rw:peek-reader reader) + `(#\- #\- ,@boundary) + `(#\- #\- ,@boundary #\- #\-))))) + +(defun post-parameters (method multipart/form-data) + (when (eq :post method) + (loop + for x in multipart/form-data + collect (destructuring-bind (tag &key headers body) x + (assert (eq :part tag)) + (cons (let ((y (cdr (assoc "Content-Disposition" headers + :test #'equal)))) + (assert (assoc "form-data" y :test #'equal)) + ;;(cdr (assoc "filename" y :test #'equal)) + ;;("Content-Type" ("application/octet-stream")) + (cdr (assoc "name" y :test #'equal))) + (when body + (coerce body 'string))))))) + (defun server-handler (stream handler) (let ((r (rw:peek-reader (rw:char-reader stream)))) (multiple-value-bind (method query protocol) (next-query r) @@ -237,18 +308,15 @@ (multiple-value-bind (protocol2 code message headers2 body) (funcall handler :write stream method query protocol headers (when (eq :post method) - #+nil ;; TODO post - (let ((n (cdr (assoc "Content-Length" headers :test #'equal)))) - (when n - (funcall handler - :read - (rw:shorter-reader - (rw:byte-reader stream) - (rw:next-z0 (rw:peek-reader (rw:reader n)))) - method - query - protocol - headers))))) + (next-multipart/form-data + (rw:shorter-reader + r + (cdr (assoc "Content-Length" headers :test #'equal))) + (coerce + (cdr (assoc "boundary" + (cdr (assoc "Content-Type" headers :test #'equal)) + :test #'equal)) + 'list)))) (write-status stream protocol2 code message) (write-headers (or headers2 '(("Connection" . "close") diff --git a/rw.lisp b/rw.lisp @@ -150,7 +150,7 @@ (setf (aref z i) x))))) (defun next-z0 (reader) - (let ((x (rw:till reader '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 \9) t))) + (let ((x (rw:till reader '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) t))) (when x (parse-integer (coerce x 'string))))) ;; TODO better diff --git a/ui.lisp b/ui.lisp @@ -84,13 +84,13 @@ ;;(print (list :@@@-a env1 svals)) (unwind-protect (funcall fn - (lambda (k p) + (lambda (k p nargs) (let ((v (getf actions1 k))) - (print (list :@@@ k p v)) + ;;(print (list :@@@ k p method v)) (when v - (if p - (funcall v p) - (funcall v))))) + (ecase nargs + (:arg0 (funcall v)) + (:arg1 (funcall v p #+nil(or p ""))))))) (lambda () (unless env1 (setq svals (delete cached svals))))) @@ -127,11 +127,11 @@ (destructuring-bind (k &rest v) x (let ((kk (when (char= #\z (char k 0)) (parse36 (subseq k 1))))) - (funcall dispatch kk v)))) - (funcall dispatch aid nil) + (funcall dispatch kk v :arg1)))) + (funcall dispatch aid nil :arg0) `(:redirect ,(funcall construct sid (pretty36 aid) *renv*))) (:get - (funcall dispatch aid nil) + (funcall dispatch aid nil :arg0) (funcall clear) (flet ((next (v) (let ((k (incf n)))