]> code.delx.au - gnu-emacs-elpa/blobdiff - emacs-web-server.el
url-encoded parameters
[gnu-emacs-elpa] / emacs-web-server.el
index 46ae4b8ad34af2407512d82ce50707aafde2ab58..2d276d58ed7c50183430af0df3345311d0e958d1 100644 (file)
@@ -8,8 +8,11 @@
 
 ;;; Code:
 (require 'emacs-web-server-status-codes)
-(require 'mail-parse)
+(require 'mail-parse)             ; to parse multipart data in headers
+(require 'mm-encode)              ; to look-up mime types for files
+(require 'url-util)               ; to decode url-encoded params
 (require 'eieio)
+(eval-when-compile (require 'cl))
 (require 'cl-lib)
 
 (defclass ews-server ()
@@ -67,7 +70,8 @@ function.
    8080)
 
 "
-  (let ((server (make-instance 'ews-server :handler handler :port port)))
+  (let ((server (make-instance 'ews-server :handler handler :port port))
+        (log (when log-buffer (get-buffer-create log-buffer))))
     (setf (process server)
           (apply
            #'make-network-process
@@ -77,16 +81,17 @@ function.
            :server t
            :nowait t
            :family 'ipv4
-           :plist (list :server server)
-           :log (when log-buffer
-                  (lexical-let ((buf log-buffer))
-                    (lambda (server client message)
-                      (let ((c (process-contact client)))
-                        (with-current-buffer buf
-                          (goto-char (point-max))
-                          (insert (format "%s\t%s\t%s\t%s"
-                                          (format-time-string ews-time-format)
-                                          (first c) (second c) message)))))))
+           :plist (append (list :server server)
+                          (when log (list :log-buffer log)))
+           :log (when log
+                  (lambda (proc client message)
+                    (let ((c (process-contact client))
+                          (buf (plist-get (process-plist proc) :log-buffer)))
+                      (with-current-buffer buf
+                        (goto-char (point-max))
+                        (insert (format "%s\t%s\t%s\t%s"
+                                        (format-time-string ews-time-format)
+                                        (first c) (second c) message))))))
            network-args))
     (push server ews-servers)
     server))
@@ -97,16 +102,27 @@ function.
   (mapc #'delete-process (append (mapcar #'car (clients server))
                                  (list (process server)))))
 
-(defun ews-parse (string)
+(defvar ews-http-common-methods '(GET HEAD POST PUT DELETE TRACE)
+  "HTTP methods from http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html.")
+
+(defvar ews-http-method-rx
+  (format "^\\(%s\\) \\([^[:space:]]+\\) \\([^[:space:]]+\\)$"
+          (mapconcat #'symbol-name ews-http-common-methods "\\|")))
+
+(defun ews-parse (proc string)
   (cl-flet ((to-keyword (s) (intern (concat ":" (upcase (match-string 1 s))))))
     (cond
-     ((string-match
-       "^\\(GET\\|POST\\) \\([^[:space:]]+\\) \\([^[:space:]]+\\)$" string)
-      (list (cons (to-keyword (match-string 1 string)) (match-string 2 string))
-            (cons :TYPE (match-string 3 string))))
+     ((string-match ews-http-method-rx string)
+      (let ((method (to-keyword (match-string 1 string)))
+            (url (match-string 2 string)))
+        (if (string-match "?" url)
+            (cons (cons method (substring url 0 (match-beginning 0)))
+                  (url-parse-query-string (url-unhex-string
+                                           (substring url (match-end 0))) ))
+          (list (cons method url)))))
      ((string-match "^\\([^[:space:]]+\\): \\(.*\\)$" string)
       (list (cons (to-keyword string) (match-string 2 string))))
-     (:otherwise (error "[ews] bad header: %S" string) nil))))
+     (:otherwise (ews-error proc "bad header: %S" string) nil))))
 
 (defun ews-trim (string)
   (while (and (> (length string) 0)
@@ -117,25 +133,26 @@ function.
   string)
 
 (defun ews-parse-multipart/form (string)
-  (when (string-match "[^[:space:]]" string) ; ignore empty
-    (unless (string-match "Content-Disposition:[[:space:]]*\\(.*\\)\r\n" string)
-      (error "missing Content-Disposition for multipart/form element."))
+  ;; ignore empty and non-content blocks
+  (when (string-match "Content-Disposition:[[:space:]]*\\(.*\\)\r\n" string)
     (let ((dp (mail-header-parse-content-disposition (match-string 1 string))))
       (cons (cdr (assoc 'name (cdr dp)))
-            (cons (cons 'content (ews-trim (substring string (match-end 0))))
-                  (cdr dp))))))
+            (ews-trim (substring string (match-end 0)))))))
 
 (defun ews-filter (proc string)
   (with-slots (handler clients) (plist-get (process-plist proc) :server)
     (unless (assoc proc clients)
       (push (cons proc (make-instance 'ews-client)) clients))
-    (let ((client (cdr (assoc proc clients))))
-      (when (ews-do-filter client string)
-        (when (ews-call-handler proc (cdr (headers client)) handler)
-          (setq clients (assq-delete-all proc clients))
-          (delete-process proc))))))
+    (let ((c (cdr (assoc proc clients))))
+      (when (not (eq (catch 'close-connection
+                       (if (ews-do-filter proc c string)
+                           (ews-call-handler proc (cdr (headers c)) handler)
+                         :keep-open))
+                     :keep-open))
+        (setq clients (assq-delete-all proc clients))
+        (delete-process proc)))))
 
-(defun ews-do-filter (client string)
+(defun ews-do-filter (proc client string)
   "Return non-nil when finished and the client may be deleted."
   (with-slots (leftover boundary headers) client
     (let ((pending (concat leftover string))
@@ -150,7 +167,13 @@ function.
             (cond
              ;; Double \r\n outside of post data means we are done
              ;; w/headers and should call the handler.
-             ((= last-index index)
+             ((and (not boundary) (= last-index index))
+              (throw 'finished-parsing-headers t))
+             ;; Parse a URL
+             ((eq boundary :application/x-www-form-urlencoded)
+              (mapc (lambda (pair) (setcdr (last headers) (list pair)))
+                    (url-parse-query-string
+                     (ews-trim (substring pending last-index))))
               (throw 'finished-parsing-headers t))
              ;; Build up multipart data.
              (boundary
@@ -164,15 +187,19 @@ function.
                 (throw 'finished-parsing-headers t)))
              ;; Standard header parsing.
              (:otherwise
-              (let ((this (ews-parse (substring pending last-index index))))
+              (let ((this (ews-parse proc (substring pending last-index index))))
                 (if (and (caar this) (eql (caar this) :CONTENT-TYPE))
                     (cl-destructuring-bind (type &rest data)
                         (mail-header-parse-content-type (cdar this))
-                      (unless (string= type "multipart/form-data")
-                        (error "TODO: handle content type %S" type))
-                      (when (assoc 'boundary data)
-                        (setq boundary (cdr (assoc 'boundary data)))
-                        (setq delimiter (concat "\r\n--" boundary))))
+                      (cond
+                       ((string= type "multipart/form-data")
+                        (when (assoc 'boundary data)
+                          (setq boundary (cdr (assoc 'boundary data)))
+                          (setq delimiter (concat "\r\n--" boundary))))
+                       ((string= type "application/x-www-form-urlencoded")
+                        (setq boundary (intern (concat ":" (downcase type)))))
+                       (:otherwise
+                        (ews-error proc "TODO: handle content type: %S" type))))
                   (setcdr (last headers) this)))))
             (setq last-index tmp)))
         (setq leftover (ews-trim (substring pending last-index)))
@@ -188,9 +215,24 @@ function.
                              (string-match (cdr match)
                                            (cdr (assoc (car match) request))))
                         (and (functionp match) (funcall match request)))
-                (throw 'matched-handler (funcall function proc request)))))
+                (throw 'matched-handler
+                       (condition-case e
+                           (funcall function proc request)
+                         (error (ews-error proc "Caught Error: %S" e)))))))
           handler)
-    (error "[ews] no handler matched request:%S" request)))
+    (ews-error proc "no handler matched request: %S" request)))
+
+(defun ews-error (proc msg &rest args)
+  (let ((buf (plist-get (process-plist proc) :log-buffer))
+        (c (process-contact proc)))
+    (when buf
+      (with-current-buffer buf
+        (goto-char (point-max))
+        (insert (format "%s\t%s\t%s\tEWS-ERROR: %s"
+                        (format-time-string ews-time-format)
+                        (first c) (second c)
+                        (apply #'format msg args)))))
+    (apply #'ews-send-500 proc msg args)))
 
 \f
 ;;; Convenience functions to write responses
@@ -205,5 +247,36 @@ Currently CODE should be an HTTP status code, see
     (setcdr (last headers) (list "" ""))
     (process-send-string proc (mapconcat #'identity headers "\r\n"))))
 
+(defun ews-send-500 (proc &rest msg-and-args)
+  "Send 500 \"Internal Server Error\" to PROC with an optional message."
+  (ews-response-header proc 500
+    '("Content-type" . "text/plain"))
+  (process-send-string proc (if msg-and-args
+                                (apply #'format msg-and-args)
+                              "500 Internal Server Error"))
+  (throw 'close-connection nil))
+
+(defun ews-send-404 (proc &rest msg-and-args)
+  "Send 404 \"Not Found\" to PROC with an optional message."
+  (ews-response-header proc 404
+    '("Content-type" . "text/plain"))
+  (process-send-string proc (if msg-and-args
+                                (apply #'format msg-and-args)
+                              "404 Not Found"))
+  (throw 'close-connection nil))
+
+(defun ews-send-file (proc path &optional mime-type)
+  "Send PATH to PROC.
+Optionally explicitly set MIME-TYPE, otherwise it is guessed by
+`mm-default-file-encoding'."
+  (let ((mime (or mime-type
+                  (mm-default-file-encoding path)
+                  "application/octet-stream")))
+    (ews-response-header proc 200 (cons "Content-type" mime))
+    (process-send-string proc
+      (with-temp-buffer
+        (insert-file-contents-literally path)
+        (buffer-string)))))
+
 (provide 'emacs-web-server)
 ;;; emacs-web-server.el ends here