File: daemon.lisp

package info (click to toggle)
araneida 0.90.1-dfsg-6
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 696 kB
  • ctags: 643
  • sloc: lisp: 4,878; perl: 166; sh: 109; makefile: 34
file content (150 lines) | stat: -rw-r--r-- 5,878 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
(in-package :araneida)

(defvar *araneida-mode* :standard
  "Defines what mode Araneida operates in. Controls things
such as how headers are handled.

:standard - default. Araneida-like operation
:tbnl     - suitable for use as a TBNL frontend. TBNL will automatically put
            Araneida into this mode on load.")

(defun read-http-line (stream &optional eof-error eof-value)
  ;; read a line terminated with a LF, even under old MacOS
  (with-output-to-string (o)
    (loop for char = (read-char stream eof-error eof-value)
          while (and (not (eql char eof-value))
                     (not (eql char #.(code-char 10))))
          do (write-char char o))))

(defun parse-protocol-version (string)
  (let ((f (position #\/ string))
	(d (position #\. string))
	(l (length string)))
    (+ (parse-integer string :start (1+ f) :end d)
       (if d
	   (/ (parse-integer string :start (1+ d))
	      (expt 10 (- l d 1)))
	   0))))

(defun read-request-from-stream (listener stream)
  (destructuring-bind (method url-string &optional protocol)
      (split (read-http-line stream t) 3 '(#\Space))
    (let ((headers (and protocol (read-headers stream))))
      (ecase *araneida-mode*
	(:standard
	 (let* ((http-version
		 (or (and protocol (parse-protocol-version protocol)) 0.9))
		(content-length (parse-integer
				 (or (header-value :content-length headers) "0")))
		(content-type (header-value :content-type headers))
		(form-data-mime-type "multipart/form-data")
		(form-data-p (and content-type
				  (>= (length content-type) (length form-data-mime-type))
				  (equal form-data-mime-type 
					 (subseq (header-value :content-type headers)
						 0
						 (length form-data-mime-type)))))
		(body (and (> content-length 0)
		     ;; The make-array form sounds good but breaks on
                     ;; streams (like Allegro's multivalent sockets)
                     ;; that do not have some kind of character
                     ;; element type, because parse-body (and other
                     ;; code) assumes it gets a string, not a vector
                     ;; of character codes. So I replaced it by
                     ;; make-string (Arthur Lemmens). (merged by Alan Shields [14 November 2005])
		     ;;
		     ;; doesn't seem to break SBCL or CMUCL -- Alan Shields [14 November 2005]
			   (make-string content-length)))
		(len (and (> content-length 0)
			  (read-sequence body stream)))
		#+lispworks
		(parsed-body (if (and body (not form-data-p))
				 (parse-body (map 'string
						  ;; for some reason lispworks wants an array of code characters
						  ;; according to Bob Hutchinson (hutch at recursive.ca)
						  ;; -- Alan Shields [14 November 2005]
						  (lambda (c) (code-char c))
						  body)
					     body '(#\&) len)
			       nil))
		#-lispworks
		(parsed-body (if (and body (not form-data-p))
				 (parse-body body '(#\&) len)
			       nil))
		(url (merge-url 
		      ;; it may be argued that we're going to hell for this, but
		      ;; (header-value :host) may in fact be host:port and I'm
		      ;; not about to pick it apart now just so we can use
		      ;; make-url instead 
		      (parse-urlstring
		       (format nil "http://~A/"
			       (or (header-value :host headers)
				   (http-listener-default-hostname listener))))
		      url-string)))
	   (make-instance 'request 
			  :url url
			  :urlstring (urlstring url)
			  :method (intern (nstring-upcase method) 'keyword)
			  :http-version http-version
			  :body parsed-body
			  :unparsed-body body
			  :stream stream :headers headers )))
	(:tbnl
	 (values method url-string protocol headers))))))

(defun parse-body (body-string &optional (delimiters '(#\&)) end)
  "Parse BODY-STRING returning list of (var val) pairs"
  (mapcar (lambda (x)
            (mapcar #'urlstring-unescape (split-sequence #\= x :count 2)))
          (split-sequence-if (lambda (x) (member x delimiters))
			     body-string :end end)))

(defun read-folded-line (stream &optional eof-error-p eof-value)
  "Read a complete logical header line, including folded continuation lines."
  (with-output-to-string (o)
    (loop
     (let* ((l (read-http-line stream eof-error-p eof-value))
	    (end (position-if (lambda (x) (or (eql x (code-char 10))
					      (eql x (code-char 13))))
			      l))
	    (next (and (if end (> end 0) t)
		       (> (length l) 0)
		       (peek-char nil stream nil nil))))
       (write-sequence l o :end end)
       (unless (or (eql next #\Space) (eql next #\Tab)) (return))))))

;;; Unless you're me (and probably not even then), you should be
;;; using REQUEST-HEADER not this:

(defun header-value (name header-list)
  "Get the value of the header named NAME in HEADER-LIST"
  (cadr (assoc name header-list)))

(defun read-headers (stream)
  (let ((headers nil))
    (do ((line (read-folded-line stream t) (read-folded-line stream t)))
        ((or (not line) (zerop (length line))) headers)
      
      ;; RFC 1945: "Each header field consists of a name followed
      ;; immediately by a colon (":"), a single space (SP) character,
      ;; and the field value. Field names are case- insensitive."
      (let* ((colon-pos (position #\: line))
	     (keyword (subseq line 0 colon-pos))
	     (value (subseq line (+ 2 colon-pos)))
	     (keyword-symbol (intern (string-upcase keyword) :keyword))
	     (keyword-value (assoc keyword-symbol  headers)))
	(ecase *araneida-mode*
	  (:standard (if keyword-value
			 (setf (cdr (last keyword-value)) (list value))
			 (setf headers (acons keyword-symbol (list value) headers))))
	  (:tbnl     (let ((name (if (stringp keyword)
				     keyword
				     (format nil "~a" keyword)))
			   (val  (if (stringp value)
				     value
				     (format nil "~a" value))))
		       (if keyword-value
			   (rplacd (assoc name headers) val)
			   (push (cons name val) headers)))))))
    headers))