]> gitweb.factorcode.org Git - factor.git/blob - basis/http/http.factor
2a5a19036f64cb7b18969233770b4fa65cb09102
[factor.git] / basis / http / http.factor
1 ! Copyright (C) 2003, 2008 Slava Pestov.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors kernel combinators math namespaces
4 assocs assocs.lib sequences splitting sorting sets debugger
5 strings vectors hashtables quotations arrays byte-arrays
6 math.parser calendar calendar.format present
7
8 io io.encodings io.encodings.iana io.encodings.binary
9 io.encodings.8-bit
10
11 unicode.case unicode.categories qualified
12
13 urls
14
15 http.parsers ;
16
17 EXCLUDE: fry => , ;
18
19 IN: http
20
21 : crlf ( -- ) "\r\n" write ;
22
23 : read-crlf ( -- bytes )
24     "\r" read-until
25     [ CHAR: \r assert= read1 CHAR: \n assert= ] when* ;
26
27 : (read-header) ( -- alist )
28     [ read-crlf dup f like ] [ parse-header-line ] [ drop ] produce ;
29
30 : process-header ( alist -- assoc )
31     f swap [ [ swap or dup ] dip swap ] assoc-map nip
32     [ ?push ] histogram [ "; " join ] assoc-map
33     >hashtable ;
34
35 : read-header ( -- assoc )
36     (read-header) process-header ;
37
38 : header-value>string ( value -- string )
39     {
40         { [ dup timestamp? ] [ timestamp>http-string ] }
41         { [ dup array? ] [ [ header-value>string ] map "; " join ] }
42         [ present ]
43     } cond ;
44
45 : check-header-string ( str -- str )
46     #! http://en.wikipedia.org/wiki/HTTP_Header_Injection
47     dup "\r\n\"" intersect empty?
48     [ "Header injection attack" throw ] unless ;
49
50 : write-header ( assoc -- )
51     >alist sort-keys [
52         [ check-header-string write ": " write ]
53         [ header-value>string check-header-string write crlf ] bi*
54     ] assoc-each crlf ;
55
56 TUPLE: cookie name value version comment path domain expires max-age http-only secure ;
57
58 : <cookie> ( value name -- cookie )
59     cookie new
60         swap >>name
61         swap >>value ;
62
63 : parse-set-cookie ( string -- seq )
64     [
65         f swap
66         (parse-set-cookie)
67         [
68             swap {
69                 { "version" [ >>version ] }
70                 { "comment" [ >>comment ] }
71                 { "expires" [ cookie-string>timestamp >>expires ] }
72                 { "max-age" [ string>number seconds >>max-age ] }
73                 { "domain" [ >>domain ] }
74                 { "path" [ >>path ] }
75                 { "httponly" [ drop t >>http-only ] }
76                 { "secure" [ drop t >>secure ] }
77                 [ <cookie> dup , nip ]
78             } case
79         ] assoc-each
80         drop
81     ] { } make ;
82
83 : parse-cookie ( string -- seq )
84     [
85         f swap
86         (parse-cookie)
87         [
88             swap {
89                 { "$version" [ >>version ] }
90                 { "$domain" [ >>domain ] }
91                 { "$path" [ >>path ] }
92                 [ <cookie> dup , nip ]
93             } case
94         ] assoc-each
95         drop
96     ] { } make ;
97
98 : check-cookie-string ( string -- string' )
99     dup "=;'\"\r\n" intersect empty?
100     [ "Bad cookie name or value" throw ] unless ;
101
102 : unparse-cookie-value ( key value -- )
103     {
104         { f [ drop ] }
105         { t [ check-cookie-string , ] }
106         [
107             {
108                 { [ dup timestamp? ] [ timestamp>cookie-string ] }
109                 { [ dup duration? ] [ duration>seconds number>string ] }
110                 { [ dup real? ] [ number>string ] }
111                 [ ]
112             } cond
113             check-cookie-string "=" swap check-cookie-string 3append ,
114         ]
115     } case ;
116
117 : check-cookie-value ( string -- string )
118     [ "Cookie value must not be f" throw ] unless* ;
119
120 : (unparse-cookie) ( cookie -- strings )
121     [
122         dup name>> check-cookie-string >lower
123         over value>> check-cookie-value unparse-cookie-value
124         "$path" over path>> unparse-cookie-value
125         "$domain" over domain>> unparse-cookie-value
126         drop
127     ] { } make ;
128
129 : unparse-cookie ( cookies -- string )
130     [ (unparse-cookie) ] map concat "; " join ;
131
132 : unparse-set-cookie ( cookie -- string )
133     [
134         dup name>> check-cookie-string >lower
135         over value>> check-cookie-value unparse-cookie-value
136         "path" over path>> unparse-cookie-value
137         "domain" over domain>> unparse-cookie-value
138         "expires" over expires>> unparse-cookie-value
139         "max-age" over max-age>> unparse-cookie-value
140         "httponly" over http-only>> unparse-cookie-value
141         "secure" over secure>> unparse-cookie-value
142         drop
143     ] { } make "; " join ;
144
145 TUPLE: request
146 method
147 url
148 version
149 header
150 post-data
151 cookies ;
152
153 : set-header ( request/response value key -- request/response )
154     pick header>> set-at ;
155
156 : <request> ( -- request )
157     request new
158         "1.1" >>version
159         <url>
160             H{ } clone >>query
161         >>url
162         H{ } clone >>header
163         V{ } clone >>cookies
164         "close" "connection" set-header
165         "Factor http.client" "user-agent" set-header ;
166
167 : header ( request/response key -- value )
168     swap header>> at ;
169
170 TUPLE: response
171 version
172 code
173 message
174 header
175 cookies
176 content-type
177 content-charset
178 body ;
179
180 : <response> ( -- response )
181     response new
182         "1.1" >>version
183         H{ } clone >>header
184         "close" "connection" set-header
185         now timestamp>http-string "date" set-header
186         "Factor http.server" "server" set-header
187         latin1 >>content-charset
188         V{ } clone >>cookies ;
189
190 M: response clone
191     call-next-method
192         [ clone ] change-header
193         [ clone ] change-cookies ;
194
195 : get-cookie ( request/response name -- cookie/f )
196     [ cookies>> ] dip '[ , _ name>> = ] find nip ;
197
198 : delete-cookie ( request/response name -- )
199     over cookies>> [ get-cookie ] dip delete ;
200
201 : put-cookie ( request/response cookie -- request/response )
202     [ name>> dupd get-cookie [ dupd delete-cookie ] when* ] keep
203     over cookies>> push ;
204
205 TUPLE: raw-response
206 version
207 code
208 message
209 body ;
210
211 : <raw-response> ( -- response )
212     raw-response new
213         "1.1" >>version ;
214
215 TUPLE: post-data raw content content-type ;
216
217 : <post-data> ( raw content-type -- post-data )
218     post-data new
219         swap >>content-type
220         swap >>raw ;
221
222 : parse-content-type-attributes ( string -- attributes )
223     " " split harvest [ "=" split1 [ >lower ] dip ] { } map>assoc ;
224
225 : parse-content-type ( content-type -- type encoding )
226     ";" split1 parse-content-type-attributes "charset" swap at
227     name>encoding over "text/" head? latin1 binary ? or ;