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:
M | http.lisp | | | 104 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- |
M | rw.lisp | | | 2 | +- |
M | ui.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)))