cl-rw

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

commit 757d4002fceb6ab0b74a8fd385c7f0c31121779d
parent 94b41f7d9d2f1652384319b367b0c27f278956dd
Author: Tomas Hlavaty <tom@logand.com>
Date:   Sun,  6 Oct 2013 22:59:40 +0200

till with look-ahead implemented, handle multiple lines in multipart/form-data

Diffstat:
Mhttp.lisp | 53+++++++++++++++++++++++++++--------------------------
Mrw.lisp | 122++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 139 insertions(+), 36 deletions(-)

diff --git a/http.lisp b/http.lisp @@ -269,23 +269,23 @@ 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 multipart-reader (reader boundary) + (let* ((start-boundary `(#\- #\- ,@boundary)) + (end-boundary `(,@start-boundary #\- #\-)) + (sentinel (list `(#\return #\newline ,@start-boundary) + `(#\return ,@start-boundary) + `(#\newline ,@start-boundary))) + (r (rw::look-ahead-reader reader (length (car sentinel))))) + (lambda () + (rw:skip r) + (when (rw:peek r) + (let ((boundary (rw:till r '(#\return #\newline)))) + (unless (equal boundary end-boundary) + (assert (equal boundary start-boundary)) + (next-eol r) + (list :part + :headers (next-headers r) + :body (rw:till r sentinel nil t)))))))) (defun post-parameters (method multipart/form-data) (when (eq :post method) @@ -309,15 +309,16 @@ (multiple-value-bind (protocol2 code message headers2 body) (funcall handler :write stream method query protocol headers (when (eq :post method) - (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)))) + (rw:slurp + (multipart-reader + (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 @@ -42,6 +42,7 @@ :search-reader :shorter-reader :skip + :slurp :till :write-octets :write-u16 @@ -92,20 +93,121 @@ (dotimes (i n/items reader) (next reader))) (list - (let ((x (or n/items '(#\space #\tab #\newline)))) + (let ((x (or n/items '(#\space #\tab #\return #\newline)))) (loop while (member (peek reader) x) do (next reader))) reader))) -(defun till (reader &optional items good) - (loop - while (let ((x (peek reader))) - (and x - (if good - (member x items) - (not (member x items))))) - collect (next reader))) +(defun slurp (reader) ;; TODO use wherever possible + (let (x) + (loop + while (setq x (next reader)) + collect x))) + +(defun till-reader (reader test) + (lambda () + (when (funcall test (peek reader)) + (next reader)))) + +(defun make-circular-list (n) + (check-type n (and fixnum (satisfies plusp))) + (let* ((b (cons nil nil)) + (e b)) + (dotimes (i (1- n)) + (push nil b)) + (setf (cdr e) b) + b)) + +;;(make-circular-list 1) +;;(make-circular-list 2) + +(defun head (reader test) + (funcall reader 'head test)) + +(defun look-ahead-reader (reader n) ;; TODO optimize, like streaming regexp or knuth-morris-pratt + (let (done + (m 0) + (b (make-circular-list n))) + (flet ((fetch () + (prog1 (car b) + (if (setf (car b) (next reader)) + (setq m (if (< m n) (1+ m) m)) + (setq done t)) + (setq b (cdr b))))) + (dotimes (i n) + (fetch)) + (lambda (&optional msg test) + (flet ((%next () + (if done + (let ((z (car b))) + (when (plusp m) + (decf m)) + (when z + (setq b (cdr b)) + z)) + (fetch)))) + (ecase msg + (peek + (unless (or done (car b)) + (%next)) + (car b)) + (head (funcall test b m)) + ((nil) (%next)))))))) + +;;(till (look-ahead-reader (reader '(0 1 2 3 4 5 6 7 8)) 4)) +;;(till (look-ahead-reader (reader '(0 1 2)) 4)) +;;(till (look-ahead-reader (reader nil) 4)) + +(defun look-ahead-till-reader (reader test) + (lambda () + (when (head reader test) + (next reader)))) + +(defun %head (prefix list &optional n) + (do ((a prefix (cdr a)) + (b list (cdr b)) + (i 0 (1+ i))) + ((not (and a b)) + (not a)) + (when n + (unless (< i n) + (return-from %head nil))) + (unless (eql (car a) (car b)) + (return-from %head nil)))) + +;; (%head '(1 2 3) '(2 3 4 5 6)) +;; (%head '(1 2 3) '(1 2 3 4 5 6)) +;; (%head '(1 2 3) '(1 2)) +;; (%head '(1 2 3) '(1 2 3 4 5 6) 3) +;; (%head '(1 2 3) '(1 2 3 4 5 6) 2) + +#+nil +(with-open-file (s "/etc/passwd") + (let* ((sentinel '#.(coerce "user" 'list)) + (r (look-ahead-reader (char-reader s) (length sentinel)))) + (list (coerce (till r (list sentinel) nil t) 'string) + (coerce (till r '(#\:)) 'string) + (coerce (till r (list sentinel) nil t) 'string) + (coerce (till r '(#\:)) 'string) + (coerce (till r (list sentinel) nil t) 'string) + (coerce (till r '(#\:)) 'string) + (coerce (till r (list sentinel) nil t) 'string)))) + +(defun till (reader &optional items good look-ahead) ;; TODO till vs until? + (slurp + (if look-ahead + (look-ahead-till-reader + reader + (if good + (lambda (b m) + (member b items :test (lambda (b p) (%head p b m)))) + (lambda (b m) + (not (member b items :test (lambda (b p) (%head p b m))))))) + (till-reader reader + (if good + (lambda (x) (member x items)) + (lambda (x) (not (member x items)))))))) ;;(till (peek-reader (reader '(0 1 2 3 4))) '(3)) ;;(till (peek-reader (reader '(0 1 2 3 4))) '(0 1) t) @@ -113,7 +215,7 @@ ;;(till (skip (peek-reader (reader #(0 1 2 3 4))) 1) '(3)) ;;(with-open-file (s "/etc/passwd") (till (peek-reader (char-reader s)) '(#\:))) -(defun search-reader (reader needle) +(defun search-reader (reader needle) ;; TODO remove (let ((all (till (peek-reader reader))) ;; TODO optimize? use kmp algorithm (start 0)) (lambda ()