prepare for release 1.2.12
[hunchentoot.git] / headers.lisp
blobe60c009e28911352f3f7e31854c2cb9dbee08d64
1 ;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: HUNCHENTOOT; Base: 10 -*-
3 ;;; Copyright (c) 2004-2010, Dr. Edmund Weitz. All rights reserved.
5 ;;; Redistribution and use in source and binary forms, with or without
6 ;;; modification, are permitted provided that the following conditions
7 ;;; are met:
9 ;;; * Redistributions of source code must retain the above copyright
10 ;;; notice, this list of conditions and the following disclaimer.
12 ;;; * Redistributions in binary form must reproduce the above
13 ;;; copyright notice, this list of conditions and the following
14 ;;; disclaimer in the documentation and/or other materials
15 ;;; provided with the distribution.
17 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
18 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
21 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 (in-package :hunchentoot)
31 (defgeneric write-header-line (key value stream)
32 (:documentation "Accepts a string KEY and a Lisp object VALUE and
33 writes them directly to the client as an HTTP header line.")
34 (:method (key (string string) stream)
35 (write-string key stream)
36 (write-char #\: stream)
37 (write-char #\Space stream)
38 (let ((start 0))
39 (loop
40 (let ((end (or (position #\Newline string :start start)
41 (length string))))
42 ;; skip empty lines, as they confuse certain HTTP clients
43 (unless (eql start end)
44 (unless (zerop start)
45 (write-char #\Tab stream))
46 (write-string string stream :start start :end end)
47 (write-char #\Return stream)
48 (write-char #\Linefeed stream))
49 (setf start (1+ end))
50 (when (<= (length string) start)
51 (return))))))
52 (:method (key value stream)
53 (write-header-line key (princ-to-string value) stream)))
55 (defun maybe-add-charset-to-content-type-header (content-type external-format)
56 "Given the contents of a CONTENT-TYPE header, add a charset=
57 attribute describing the given EXTERNAL-FORMAT if no charset=
58 attribute is already present and the content type is a text content
59 type. Returns the augmented content type."
60 (if (and (cl-ppcre:scan "(?i)^text" content-type)
61 (not (cl-ppcre:scan "(?i);\\s*charset=" content-type)))
62 (format nil "~A; charset=~(~A~)" content-type (flex:external-format-name external-format))
63 content-type))
65 (defun start-output (return-code &optional (content nil content-provided-p))
66 "Sends all headers and maybe the content body to
67 *HUNCHENTOOT-STREAM*. Returns immediately and does nothing if called
68 more than once per request. Called by PROCESS-REQUEST and/or
69 SEND-HEADERS. The RETURN-CODE argument represents the integer return
70 code of the request. The corresponding reason phrase is determined by
71 calling the REASON-PHRASE function. The CONTENT provided represents
72 the body data to send to the client, if any. If it is not specified,
73 no body is written to the client. The handler function is expected to
74 directly write to the stream in this case.
76 Returns the stream that is connected to the client."
77 (let* ((chunkedp (and (acceptor-output-chunking-p *acceptor*)
78 (eq (server-protocol *request*) :http/1.1)
79 ;; only turn chunking on if the content
80 ;; length is unknown at this point...
81 (null (or (content-length*) content-provided-p))))
82 (request-method (request-method *request*))
83 (head-request-p (eq request-method :head))
84 content-modified-p)
85 (multiple-value-bind (keep-alive-p keep-alive-requested-p)
86 (keep-alive-p *request*)
87 (when keep-alive-p
88 (setq keep-alive-p
89 ;; use keep-alive if there's a way for the client to
90 ;; determine when all content is sent (or if there
91 ;; is no content)
92 (or chunkedp
93 head-request-p
94 (eql (return-code*) +http-not-modified+)
95 (content-length*)
96 content)))
97 ;; now set headers for keep-alive and chunking
98 (when chunkedp
99 (setf (header-out :transfer-encoding) "chunked"))
100 (cond (keep-alive-p
101 (setf *close-hunchentoot-stream* nil)
102 (when (and (acceptor-read-timeout *acceptor*)
103 (or (not (eq (server-protocol *request*) :http/1.1))
104 keep-alive-requested-p))
105 ;; persistent connections are implicitly assumed for
106 ;; HTTP/1.1, but we return a 'Keep-Alive' header if the
107 ;; client has explicitly asked for one
108 (setf (header-out :connection) "Keep-Alive"
109 (header-out :keep-alive)
110 (format nil "timeout=~D" (acceptor-read-timeout *acceptor*)))))
111 (t (setf (header-out :connection) "Close"))))
112 (unless (and (header-out-set-p :server)
113 (null (header-out :server)))
114 (setf (header-out :server) (or (header-out :server)
115 (acceptor-server-name *acceptor*))))
116 (setf (header-out :date) (rfc-1123-date))
117 (when (and (stringp content)
118 (not content-modified-p)
119 (starts-with-one-of-p (or (content-type*) "")
120 *content-types-for-url-rewrite*))
121 ;; if the Content-Type header starts with one of the strings
122 ;; in *CONTENT-TYPES-FOR-URL-REWRITE* then maybe rewrite the
123 ;; content
124 (setq content (maybe-rewrite-urls-for-session content)))
125 (when (stringp content)
126 ;; if the content is a string, convert it to the proper external format
127 (setf content (string-to-octets content :external-format (reply-external-format*))
128 (content-type*) (maybe-add-charset-to-content-type-header (content-type*)
129 (reply-external-format*))))
130 (when content
131 ;; whenever we know what we're going to send out as content, set
132 ;; the Content-Length header properly; maybe the user specified
133 ;; a different content length, but that will wrong anyway
134 (setf (header-out :content-length) (length content)))
135 ;; send headers only once
136 (when *headers-sent*
137 (return-from start-output))
138 (setq *headers-sent* t)
139 (send-response *acceptor*
140 *hunchentoot-stream*
141 return-code
142 :headers (headers-out*)
143 :cookies (cookies-out*)
144 :content (unless head-request-p
145 content))
146 ;; when processing a HEAD request, exit to return from PROCESS-REQUEST
147 (when head-request-p
148 (throw 'request-processed nil))
149 (when chunkedp
150 ;; turn chunking on after the headers have been sent
151 (unless (typep *hunchentoot-stream* 'chunked-stream)
152 (setq *hunchentoot-stream* (make-chunked-stream *hunchentoot-stream*)))
153 (setf (chunked-stream-output-chunking-p *hunchentoot-stream*) t))
154 *hunchentoot-stream*))
156 (defun send-response (acceptor stream status-code
157 &key headers cookies content)
158 "Send a HTTP response to the STREAM and log the event in ACCEPTOR.
159 STATUS-CODE is the HTTP status code used in the response. HEADERS
160 and COOKIES are used to create the response header. If CONTENT is
161 provided, it is sent as the response body.
163 If *HEADER-STREAM* is not NIL, the response headers are written to
164 that stream when they are written to the client.
166 STREAM is returned."
167 (when content
168 (setf (content-length*) (length content)))
169 (when (content-length*)
170 (if (assoc :content-length headers)
171 (setf (cdr (assoc :content-length headers)) (content-length*))
172 (push (cons :content-length (content-length*)) headers)))
173 ;; access log message
174 (acceptor-log-access acceptor :return-code status-code)
175 ;; Read post data to clear stream - Force binary mode to avoid OCTETS-TO-STRING overhead.
176 (raw-post-data :force-binary t)
177 (let* ((client-header-stream (flex:make-flexi-stream stream :external-format +latin-1+))
178 (header-stream (if *header-stream*
179 (make-broadcast-stream *header-stream* client-header-stream)
180 client-header-stream)))
181 ;; start with status line
182 (format header-stream "HTTP/1.1 ~D ~A~C~C" status-code (reason-phrase status-code) #\Return #\Linefeed)
183 ;; write all headers from the REPLY object
184 (loop for (key . value) in headers
185 when value
186 do (write-header-line (as-capitalized-string key) value header-stream))
187 ;; now the cookies
188 (loop for (nil . cookie) in cookies
189 do (write-header-line "Set-Cookie" (stringify-cookie cookie) header-stream))
190 (format header-stream "~C~C" #\Return #\Linefeed))
191 ;; now optional content
192 (when content
193 (write-sequence content stream)
194 (finish-output stream))
195 stream)
197 (defun send-headers ()
198 "Sends the initial status line and all headers as determined by the
199 REPLY object *REPLY*. Returns a binary stream to which the body of
200 the reply can be written. Once this function has been called, further
201 changes to *REPLY* don't have any effect. Also, automatic handling of
202 errors \(i.e. sending the corresponding status code to the browser,
203 etc.) is turned off for this request. If your handlers return the
204 full body as a string or as an array of octets you should NOT call
205 this function.
207 This function does not return control to the caller during HEAD
208 request processing."
209 (start-output (return-code*)))
211 (defun read-initial-request-line (stream)
212 "Reads and returns the initial HTTP request line, catching permitted
213 errors and handling *BREAK-EVEN-WHILE-READING-REQUEST-TYPE-P*. If no
214 request could be read, returns NIL. At this point, both an
215 end-of-file as well as a timeout condition are normal; end-of-file
216 will occur when the client has decided to not send another request but
217 to close the connection instead, a timeout indicates that the
218 connection timeout established by Hunchentoot has expired and we do
219 not want to wait for another request any longer."
220 (handler-case
221 (let ((*current-error-message* "While reading initial request line:"))
222 (with-mapped-conditions ()
223 (read-line* stream)))
224 ((or end-of-file #-:lispworks usocket:timeout-error) ())))
226 (defun send-bad-request-response (stream &optional additional-info)
227 "Send a ``Bad Request'' response to the client."
228 (write-sequence (flex:string-to-octets
229 (format nil "HTTP/1.0 ~D ~A~C~CConnection: close~C~C~C~CYour request could not be interpreted by this HTTP server~C~C~@[~A~]~C~C"
230 +http-bad-request+ (reason-phrase +http-bad-request+) #\Return #\Linefeed
231 #\Return #\Linefeed #\Return #\Linefeed #\Return #\Linefeed additional-info #\Return #\Linefeed))
232 stream))
234 (defun printable-ascii-char-p (char)
235 (<= 32 (char-code char) 126))
237 (defun get-request-data (stream)
238 "Reads incoming headers from the client via STREAM. Returns as
239 multiple values the headers as an alist, the method, the URI, and the
240 protocol of the request."
241 (with-character-stream-semantics
242 (let ((first-line (read-initial-request-line stream)))
243 (when first-line
244 (unless (every #'printable-ascii-char-p first-line)
245 (send-bad-request-response stream "Non-ASCII character in request line")
246 (return-from get-request-data nil))
247 (destructuring-bind (&optional method url-string protocol)
248 (split "\\s+" first-line :limit 3)
249 (unless url-string
250 (send-bad-request-response stream)
251 (return-from get-request-data nil))
252 (when *header-stream*
253 (format *header-stream* "~A~%" first-line))
254 (let ((headers (and protocol (read-http-headers stream *header-stream*))))
255 (unless protocol (setq protocol "HTTP/0.9"))
256 ;; maybe handle 'Expect: 100-continue' header
257 (when-let (expectations (cdr (assoc* :expect headers)))
258 (when (member "100-continue" (split "\\s*,\\s*" expectations) :test #'equalp)
259 ;; according to 14.20 in the RFC - we should actually
260 ;; check if we have to respond with 417 here
261 (let ((continue-line
262 (format nil "HTTP/1.1 ~D ~A"
263 +http-continue+
264 (reason-phrase +http-continue+))))
265 (write-sequence (map 'list #'char-code continue-line) stream)
266 (write-sequence +crlf+ stream)
267 (write-sequence +crlf+ stream)
268 (force-output stream)
269 (when *header-stream*
270 (format *header-stream* "~A~%" continue-line)))))
271 (values headers
272 (as-keyword method)
273 url-string
274 (as-keyword (trim-whitespace protocol)))))))))