001 ;;; -*- Lisp -*- mode
002 
003 (in-package #:com.paskvil.uhttp)
004 
005 (defun http-char (c1 c2 &optional (default #\Space))
006   "Function to convert HTTP-encoded character sequence to character."
007   (let ((code (parse-integer
008                (coerce (list c1 c2) 'string)
009                :radix 16
010                :junk-allowed t)))
011     (if code
012         (code-char code)
013         default)))
014 
015 (defun decode-param (s)
016   "Function to decode HTTP encoded strings."
017   (labels ((f (lst)
018              (when lst
019                (case (car lst)
020                  (#\% (cons (http-char (cadr lst) (caddr lst))
021                             (f (cdddr lst))))
022                  (#\+ (cons #\Space (f (cdr lst))))
023                  (t (cons (car lst) (f (cdr lst))))))))
024     (coerce (f (coerce s 'list)) 'string)))
025 
026 (defun parse-params-from-url (url &optional (offset 0))
027   (let ((eqpos (position #\= url :start offset))
028         (ampos (position #\& url :start offset)))
029     (if ampos
030         (if (and eqpos (< eqpos ampos))
031             (cons (cons (decode-param (subseq url offset eqpos))
032                         (decode-param (subseq url (1+ eqpos) ampos)))
033                   (parse-params-from-url url (1+ ampos)))
034             (cons (cons (decode-param (subseq url offset ampos)) "")
035                   (parse-params-from-url url (1+ ampos))))
036         (if eqpos
037             (list (cons (decode-param (subseq url offset eqpos))
038                         (decode-param (subseq url (1+ eqpos)))))
039             (let ((s (subseq url offset)))
040               (if (equal s "")
041                   nil
042                   (list (cons (decode-param s) ""))))))))
043 
044 (defun parse-url-from-header (str)
045   "Parses the URL and all GET parameters from the first
046    line of the request header - \"GET /url?params HTTP/x.x\".
047    Returns data as (url (param1 . value1) ...); second value
048    is GET/POST - type of the request."
049   (let ((pos1 (position #\Space str))
050         (pos2 (position #\Space str :from-end t)))
051     (if (and pos1 pos2 (< (1+ pos1) pos2))
052         (let* ((url (subseq str (+ pos1 2) pos2))
053                (qpos (position #\? url)))
054           (if qpos
055               (values (cons (subseq url 0 qpos)
056                             (parse-params-from-url url (1+ qpos)))
057                       (subseq str 0 pos1))
058               (values (list url)
059                       (subseq str 0 pos1))))
060         (values nil nil))))
061 
062 (defun parse-header-params (lst)
063   "Parses lines 'Info-type: Info', and returns them as array
064    of cons'es (\"Info-type\" . \"Info\")."
065   (when (and (listp lst) lst (string-not-equal (car lst) ""))
066     (let* ((str (car lst))
067            (pos (position #\: str))
068            (len (length str)))
069       (if (and pos (> len (+ pos 2)))    ; skip malformed lines, or no data
070           (cons (cons (subseq str 0 pos) (subseq str (+ pos 2)))
071                 (parse-header-params (cdr lst)))
072           (parse-header-params (cdr lst))))))
073 
074 (defun server-send-header (&key (code 200) (status "OK") (content "text/html"))
075   (format t "HTTP/1.1 ~a ~a~%" code status)
076   (format t "Server: com.paskvil.uhttp~%")
077   (format t "Content-Type: ~a~%~%" content))
078 
079 (defmacro with-accepted-client ((server url get head post) &body body)
080   "Waits for client to connect to the 'server, then binds variables
081    passed as 'url to the URL called, 'get to alist of GET parameters,
082    and 'head to the alist of the request header parameters.
083    You can also use *standard-output* for directly sending response."
084   (let ((srvstr (gensym))
085         (header-data (gensym))
086         (url-data (gensym))
087         (req-type (gensym))
088         (post-len (gensym))
089         (post-type (gensym)))
090     `(let* ((,srvstr (server-wait-for-client ,server))
091             (*standard-output* (usocket:socket-stream ,srvstr)))
092        (unwind-protect
093             (progn
094               (let* ((,header-data (server-read-all-lines ,srvstr :stop-on ""))
095                      (,url-data (multiple-value-list (parse-url-from-header (car ,header-data))))
096                      (,url (caar ,url-data))
097                      (,get (cdar ,url-data))
098                      (,req-type (cadr ,url-data))
099                      (,head (parse-header-params (cdr ,header-data)))
100                      (,post nil)
101                      (,post-len (cdr (assoc "Content-Length" ,head :test #'equal)))
102                      (,post-type (cdr (assoc "Content-Type" ,head :test #'equal))))
103                 (when (and (string-equal ,req-type "POST")
104                            ,post-len
105                            (< 0 (parse-integer ,post-len :junk-allowed t))
106                            ,post-type
107                            (string-equal ,post-type "application/x-www-form-urlencoded"))
108                   (setf ,post (parse-params-from-url (server-read ,srvstr :max-length ,post-len))))
109                 ,@body))
110          (server-stop ,srvstr)))))
111 
© 2011 Josef Nygrin - paskvil.com