1 ! Copyright (C) 2003, 2008 Slava Pestov.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors kernel combinators math namespaces make
4 assocs sequences splitting sorting sets debugger
5 strings vectors hashtables quotations arrays byte-arrays
6 math.parser calendar calendar.format present urls
8 io io.encodings io.encodings.iana io.encodings.binary
11 unicode.case unicode.categories qualified
19 : crlf ( -- ) "\r\n" write ;
21 : read-crlf ( -- bytes )
23 [ CHAR: \r assert= read1 CHAR: \n assert= ] when* ;
25 : (read-header) ( -- alist )
26 [ read-crlf dup f like ] [ parse-header-line ] [ drop ] produce ;
28 : collect-headers ( assoc -- assoc' )
29 H{ } clone [ '[ _ push-at ] assoc-each ] keep ;
31 : process-header ( alist -- assoc )
32 f swap [ [ swap or dup ] dip swap ] assoc-map nip
33 collect-headers [ "; " join ] assoc-map
36 : read-header ( -- assoc )
37 (read-header) process-header ;
39 : header-value>string ( value -- string )
41 { [ dup timestamp? ] [ timestamp>http-string ] }
42 { [ dup array? ] [ [ header-value>string ] map "; " join ] }
46 : check-header-string ( str -- str )
47 #! http://en.wikipedia.org/wiki/HTTP_Header_Injection
48 dup "\r\n\"" intersect empty?
49 [ "Header injection attack" throw ] unless ;
51 : write-header ( assoc -- )
53 [ check-header-string write ": " write ]
54 [ header-value>string check-header-string write crlf ] bi*
57 TUPLE: cookie name value version comment path domain expires max-age http-only secure ;
59 : <cookie> ( value name -- cookie )
64 : parse-set-cookie ( string -- seq )
70 { "version" [ >>version ] }
71 { "comment" [ >>comment ] }
72 { "expires" [ cookie-string>timestamp >>expires ] }
73 { "max-age" [ string>number seconds >>max-age ] }
74 { "domain" [ >>domain ] }
76 { "httponly" [ drop t >>http-only ] }
77 { "secure" [ drop t >>secure ] }
78 [ <cookie> dup , nip ]
84 : parse-cookie ( string -- seq )
90 { "$version" [ >>version ] }
91 { "$domain" [ >>domain ] }
92 { "$path" [ >>path ] }
93 [ <cookie> dup , nip ]
99 : check-cookie-string ( string -- string' )
100 dup "=;'\"\r\n" intersect empty?
101 [ "Bad cookie name or value" throw ] unless ;
103 : unparse-cookie-value ( key value -- )
106 { t [ check-cookie-string , ] }
109 { [ dup timestamp? ] [ timestamp>cookie-string ] }
110 { [ dup duration? ] [ duration>seconds number>string ] }
111 { [ dup real? ] [ number>string ] }
114 [ check-cookie-string ] bi@ "=" swap 3append ,
118 : check-cookie-value ( string -- string )
119 [ "Cookie value must not be f" throw ] unless* ;
121 : (unparse-cookie) ( cookie -- strings )
123 dup name>> check-cookie-string >lower
124 over value>> check-cookie-value unparse-cookie-value
125 "$path" over path>> unparse-cookie-value
126 "$domain" over domain>> unparse-cookie-value
130 : unparse-cookie ( cookies -- string )
131 [ (unparse-cookie) ] map concat "; " join ;
133 : unparse-set-cookie ( cookie -- string )
135 dup name>> check-cookie-string >lower
136 over value>> check-cookie-value unparse-cookie-value
137 "path" over path>> unparse-cookie-value
138 "domain" over domain>> unparse-cookie-value
139 "expires" over expires>> unparse-cookie-value
140 "max-age" over max-age>> unparse-cookie-value
141 "httponly" over http-only>> unparse-cookie-value
142 "secure" over secure>> unparse-cookie-value
144 ] { } make "; " join ;
154 : set-header ( request/response value key -- request/response )
155 pick header>> set-at ;
157 : <request> ( -- request )
165 "close" "connection" set-header
166 "Factor http.client" "user-agent" set-header ;
168 : header ( request/response key -- value )
181 : <response> ( -- response )
185 "close" "connection" set-header
186 now timestamp>http-string "date" set-header
187 "Factor http.server" "server" set-header
188 latin1 >>content-charset
189 V{ } clone >>cookies ;
193 [ clone ] change-header
194 [ clone ] change-cookies ;
196 : get-cookie ( request/response name -- cookie/f )
197 [ cookies>> ] dip '[ [ _ ] dip name>> = ] find nip ;
199 : delete-cookie ( request/response name -- )
200 over cookies>> [ get-cookie ] dip delete ;
202 : put-cookie ( request/response cookie -- request/response )
203 [ name>> dupd get-cookie [ dupd delete-cookie ] when* ] keep
204 over cookies>> push ;
212 : <raw-response> ( -- response )
216 TUPLE: post-data raw content content-type ;
218 : <post-data> ( raw content-type -- post-data )
223 : parse-content-type-attributes ( string -- attributes )
224 " " split harvest [ "=" split1 [ >lower ] dip ] { } map>assoc ;
226 : parse-content-type ( content-type -- type encoding )
227 ";" split1 parse-content-type-attributes "charset" swap at
228 name>encoding over "text/" head? latin1 binary ? or ;