Commit 9604b515e0b9b4dc009c73c1ed81f206f3a98e06

Thomas de Grivel 2017-08-01T21:53:34

Add support for directory listing.

diff --git a/package.lisp b/package.lisp
index 4e82d73..0ffc5e1 100644
--- a/package.lisp
+++ b/package.lisp
@@ -21,8 +21,10 @@
    :cffi-errno
    :cl-debug
    :cl-stream
+   :common-lisp
+   :dirent
    :fd-stream
-   :common-lisp)
+   :html-entities)
   #.(cl-stream:shadowing-import-from)
   (:export
    #:request
diff --git a/thot.asd b/thot.asd
index cca886c..d62dceb 100644
--- a/thot.asd
+++ b/thot.asd
@@ -12,11 +12,14 @@
                "bordeaux-set"
                "bordeaux-threads"
                "babel-stream"
+               "cffi-dirent"
                "cffi-epoll"
                "cffi-socket"
                "cl-debug"
                "cl-stream"
-               "fd-stream")
+               "fd-stream"
+               "html-entities"
+               "rol-uri")
   :components
   ((:file "package")
    (:file "thot" :depends-on ("package"))
diff --git a/thot.lisp b/thot.lisp
index f872b02..08a7dc9 100644
--- a/thot.lisp
+++ b/thot.lisp
@@ -28,6 +28,9 @@
             :type hash-table)
    (uri :accessor request-uri%
         :type string)
+   (scheme :type string)
+   (host :type string)
+   (dir :type string)
    (query :initform nil
           :accessor request-query%
           :type string)))
@@ -83,6 +86,43 @@
     (split-request-uri-and-query request))
   (slot-value request 'uri))
 
+(defun uri-scheme (uri)
+  (let ((column (position #\: uri)))
+    (unless (null column)
+      (subseq uri 0 column))))
+
+(defun request-scheme (&optional (request *request*))
+  (declare (type request request))
+  (unless (slot-boundp request 'scheme)
+    (setf (slot-value request 'scheme)
+          (uri-scheme (request-uri request))))
+  (slot-value request 'scheme))
+
+(defun uri-host (uri)
+  (let ((host (search "://" uri)))
+    (unless (null host)
+      (incf host 3)
+      (let ((host-end (position #\/ uri :start host)))
+        (subseq uri host host-end)))))
+
+(defun request-host (&optional (request *request*))
+  (declare (type request request))
+  (unless (slot-boundp request 'host)
+    (setf (slot-value request 'host)
+          (uri-host (request-uri request))))
+  (slot-value request 'host))
+
+(defun uri-dir (uri)
+  (let ((dir-end (position #\/ uri :from-end t :start 1)))
+    (subseq uri 0 dir-end)))
+
+(defun request-dir (&optional (request *request*))
+  (declare (type request request))
+  (unless (slot-boundp request 'dir)
+    (setf (slot-value request 'dir)
+          (uri-dir (request-uri request))))
+  (slot-value request 'dir))
+
 (defun request-query (&optional (request *request*))
   (declare (type request request))
   (unless (slot-boundp request 'query)
@@ -293,10 +333,64 @@ The requested url ~S was not found on this server."
                    (request-target))))
 
 (defun 404-not-found-handler ()
-  '404-not-found)
-
-(defvar *url-handlers*
-  '(404-not-found-handler))
+  '(404-not-found))
+
+(defun str (&rest parts)
+  (with-output-to-string (s)
+    (dolist (p parts)
+      (write-sequence s p))))
+
+(defun h (string)
+  (expand-entities string))
+
+(defun url-encode (string)
+  (rol-uri:%-encode string))
+
+(defun directory-index (local remote)
+  (let* ((request-dir (request-dir))
+         (dir (if (< (length request-dir)
+                     (length remote))
+                  (subseq request-dir (length remote))
+                  request-dir)))
+    (header "Content-Type: text/html")
+    (content
+     (with-output-to-string (o)
+       (flet ((w (&rest parts)
+                (dolist (p parts)
+                  (write-sequence o p))))
+       (w "<html>
+ <head>
+ </head>
+ <body>
+  <h1>" (h dir) "</h1>
+  <ul>
+")
+       (let* ((localdir (str local "/" dir))
+              (entries (dir localdir)))
+         (dolist (df entries)
+           (let* ((name (dirent-name df))
+                  (slash (if (= +dt-dir+ (dirent-type df)) "/" ""))
+                  (url (str (unless (string= "/" remote)
+                              (str (url-encode remote) "/"))
+                            (unless (string= "/" dir)
+                              (url-encode dir)) "/"
+                            (url-encode name) slash)))
+             (w "   <li><a href=\"" (h url) "\">" (h name) slash "</a></li>
+"))))
+       (w "  </ul>
+ </body>
+</html>
+"))))))
+
+(defun directory-handler (local remote)
+  `(directory-index ,local ,remote))
+
+(defparameter *url-handlers*
+  '((directory-handler "/" "/")
+    (404-not-found-handler)))
+
+(defun call (list)
+  (apply (first list) (rest list)))
 
 (defun request-cont (request reply)
   (let ((handlers *url-handlers*)
@@ -310,12 +404,12 @@ The requested url ~S was not found on this server."
       (loop
          (when (endp handlers)
            (return))
-         (let* ((handler-func (pop handlers))
-                (handler (funcall handler-func)))
+         (let* ((handler-form (pop handlers))
+                (handler (call handler-form)))
            (when handler
              (when (debug-p (or :thot :http))
-               (format t "~&~S -> ~S~%" handler-func handler))
-             (funcall handler)
+               (format t "~&~S -> ~S~%" handler-form handler))
+             (call handler)
              (flush (reply-stream% reply))
              (return)))))
     (if (string-equal "keep-alive" (request-header 'connection))