Improved error response generation. We now keep track of the last

request read on a connection, which allows us to generate the correct
type of response in case of an error which is handled through
`handle-server-error'.

Also added code to correctly escape text that is inserted into error
messages.  This is also a first step towards preventing cross-scripting
attacks through CLASH, although most of the code still has to be audited
for unfiltered passing through of user-supplied text.
This commit is contained in:
2001-02-07 14:06:14 +00:00
parent 57583b37ce
commit a803c62958
4 changed files with 56 additions and 24 deletions

View File

@ -25,7 +25,8 @@
"List of states that a connection can be in.") "List of states that a connection can be in.")
(defclass connection () (defclass connection ()
((state :initarg :state :initform :fresh :accessor connection-state))) ((state :initarg :state :initform :fresh :accessor connection-state)
(last-request :initform nil :accessor connection-last-request)))
(defgeneric connection-stream (connection)) (defgeneric connection-stream (connection))

View File

@ -96,6 +96,7 @@ request object, which references the given server and connection."))
:server server :server server
:method method :method method
:url url))) :url url)))
(setf (connection-last-request connection) request)
(parse-request-remainder request stream) (parse-request-remainder request stream)
request)))) request))))
@ -198,7 +199,7 @@ string pairs for further parsing."
specified by the clauses, and return as values the list of processed specified by the clauses, and return as values the list of processed
headers and the rest a-list of unprocessed headers. headers and the rest a-list of unprocessed headers.
The clauses are of the form ((string key) . body). For each header in The clauses are of the form ((key string) . body). For each header in
the a-list, if a clause with a matching (string-equal) string form, the a-list, if a clause with a matching (string-equal) string form,
which is evaluated, is found, then body is evaluated in an environment which is evaluated, is found, then body is evaluated in an environment
where the variables named via `header' and `value' are bound to the where the variables named via `header' and `value' are bound to the
@ -217,10 +218,8 @@ is appended to the list of unprocessed headers."
x x
(cons x nil))) (cons x nil)))
clauses) clauses)
for string = (string (if (consp clause) (car clause) clause)) for string = (string (if (consp clause) (cadr clause) clause))
for key = (if (and (consp clause) (cadr clause)) for key = (if (consp clause) (car clause) clause)
(cadr clause)
(intern (string-upcase string) "KEYWORD"))
collect `((string-equal ,header ,string) collect `((string-equal ,header ,string)
(list ,key (progn ,value ,@body)))))) (list ,key (progn ,value ,@body))))))
`(loop for (,header . ,value) in ,headers `(loop for (,header . ,value) in ,headers

View File

@ -232,3 +232,20 @@ the list of values. All other entries are kept."
(cons value (cdr entry)) (cons value (cdr entry))
(list value (cdr entry)))) (list value (cdr entry))))
(push (cons key value) result)))) (push (cons key value) result))))
;;; HTML escaping for error messages.
;;; This is especially important to avoid cross-scripting client
;;; attacks through our server.
(defun escape-text-for-html (text)
(declare (type simple-string text))
(with-output-to-string (stream)
(loop for char of-type character across text
do
(case char
(#\< (write-string "&lt;" stream))
(#\> (write-string "&gt;" stream))
(#\& (write-string "&amp;" stream))
(#\" (write-string "&quot;" stream))
(t (write-char char stream))))))

View File

@ -92,7 +92,8 @@ passed to `make-instance'."))
(:documentation (:documentation
"Forward the calculated response object to the connection.")) "Forward the calculated response object to the connection."))
(defgeneric get-error-response-class-using-server (server connection condition)
(:documentation "Calculate the response class for the error response."))
(defgeneric handle-server-error (server connection condition) (defgeneric handle-server-error (server connection condition)
(:documentation (:documentation
@ -160,6 +161,13 @@ Server Response-Header."))
:status-code status-code :status-code status-code
args)) args))
(defmethod get-error-response-class-using-server (server connection condition)
(declare (ignore condition))
(let ((last-request (connection-last-request connection)))
(if last-request
(get-response-class-using-server server last-request)
(find-class 'response/0.9))))
;;; Basic 0.9 server ;;; Basic 0.9 server
(defclass http-server/0.9 (simple-http-server) (defclass http-server/0.9 (simple-http-server)
@ -180,16 +188,18 @@ Server Response-Header."))
(defmethod handle-server-error ((server http-server/0.9) connection condition) (defmethod handle-server-error ((server http-server/0.9) connection condition)
(ignore-errors (ignore-errors
(let ((response (make-instance (let ((response
'response/0.9 (make-instance
:server server (get-error-response-using-server server connection condition)
:status-code :server server
+HTTP-Code-Internal-Server-Error+ :status-code +HTTP-Code-Internal-Server-Error+
:entity :entity
(make-instance 'string-entity (make-instance 'string-entity
:body :body
(format nil (format nil
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">~%<HTML><HEAD>~%<TITLE>500 Internal Server Error</TITLE>~%</HEAD>~%<BODY>~%<H1>Internal Server Error</H1>~%<P>An internal server error occured:</P>~%<PRE>~%~A~%</PRE>~%</BODY></HTML>" condition))))) "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">~%<HTML><HEAD>~%<TITLE>500 Internal Server Error</TITLE>~%</HEAD>~%<BODY>~%<H1>Internal Server Error</H1>~%<P>An internal server error occured:</P>~%<PRE>~%~A~%</PRE>~%</BODY></HTML>"
(escape-text-for-html
(princ-to-string condition)))))))
(write-response server connection response))) (write-response server connection response)))
(throw 'exit-connection nil)) (throw 'exit-connection nil))
@ -228,7 +238,8 @@ Server Response-Header."))
(typecase condition (typecase condition
(clash-error (clash-error
(apply #'make-instance (apply #'make-instance
'response/1.0 (get-error-response-class-using-server
server connection condition)
:server server :server server
:status-code (clash-error-code condition) :status-code (clash-error-code condition)
:entity :entity
@ -237,20 +248,24 @@ Server Response-Header."))
:body :body
(format nil "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">~%<HTML><HEAD>~%<TITLE>~3D ~A</TITLE>~%</HEAD>~%<BODY>~%<H1>~2:*~3D ~A</H1>~%<PRE>~%~A~%</PRE>~%</BODY></HTML>" (format nil "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">~%<HTML><HEAD>~%<TITLE>~3D ~A</TITLE>~%</HEAD>~%<BODY>~%<H1>~2:*~3D ~A</H1>~%<PRE>~%~A~%</PRE>~%</BODY></HTML>"
(clash-error-code condition) (clash-error-code condition)
(HTTP-Code-Description (clash-error-code condition)) (HTTP-Code-Description
condition) (clash-error-code condition))
(escape-text-for-html
(princ-to-string condition)))
(clash-error-entity-initargs condition)) (clash-error-entity-initargs condition))
(clash-error-response-initargs condition))) (clash-error-response-initargs condition)))
(error (error
(make-instance (make-instance
'response/1.0 (get-error-response-class-using-server
server connection condition)
:server server :server server
:status-code :status-code +HTTP-Code-Internal-Server-Error+
+HTTP-Code-Internal-Server-Error+
:entity :entity
(make-instance 'string-entity (make-instance 'string-entity
:body :body
(format nil (format nil
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">~%<HTML><HEAD>~%<TITLE>500 Internal Server Error</TITLE>~%</HEAD>~%<BODY>~%<H1>Internal Server Error</H1>~%<P>An internal server error occured:</P>~%<PRE>~%~A~%</PRE>~%</BODY></HTML>" condition))))))) "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">~%<HTML><HEAD>~%<TITLE>500 Internal Server Error</TITLE>~%</HEAD>~%<BODY>~%<H1>Internal Server Error</H1>~%<P>An internal server error occured:</P>~%<PRE>~%~A~%</PRE>~%</BODY></HTML>"
(escape-text-for-html
(princ-to-string condition)))))))))
(write-response server connection response))) (write-response server connection response)))
(throw 'exit-connection nil)) (throw 'exit-connection nil))