diff --git a/extra/http/basic-authentication/authors.txt b/extra/http/basic-authentication/authors.txt
deleted file mode 100644
index 44b06f94bc..0000000000
--- a/extra/http/basic-authentication/authors.txt
+++ /dev/null
@@ -1 +0,0 @@
-Chris Double
diff --git a/extra/http/basic-authentication/basic-authentication-docs.factor b/extra/http/basic-authentication/basic-authentication-docs.factor
deleted file mode 100644
index 68d6e6bf1d..0000000000
--- a/extra/http/basic-authentication/basic-authentication-docs.factor
+++ /dev/null
@@ -1,69 +0,0 @@
-! Copyright (C) 2007 Chris Double.
-! See http://factorcode.org/license.txt for BSD license.
-USING: help.markup help.syntax crypto.sha2 ;
-IN: http.basic-authentication
-
-HELP: realms
-{ $description
- "A hashtable mapping a basic authentication realm (a string) "
- "to either a quotation or a hashtable. The quotation has "
- "stack effect ( username sha-256-string -- bool ). It "
- "is expected to perform the user authentication when called." $nl
- "If the realm maps to a hashtable then the hashtable should be a "
- "mapping of usernames to sha-256 hashed passwords." $nl
- "If the 'realms' variable does not exist in the current scope then "
- "authentication will always fail." }
-{ $see-also add-realm with-basic-authentication } ;
-
-HELP: add-realm
-{ $values
- { "data" "a quotation or a hashtable" } { "name" "a string" } }
-{ $description
- "Adds the authentication data to the " { $link realms } ". 'data' can be "
- "a quotation with stack effect ( username sha-256-string -- bool ) or "
- "a hashtable mapping username strings to sha-256-string passwords." }
-{ $examples
- { $code "H{ { \"admin\" \"...\" } { \"user\" \"...\" } } \"my-realm\" add-realm" }
- { $code "[ \"...\" = swap \"admin\" = and ] \"my-realm\" add-realm" }
-}
-{ $see-also with-basic-authentication realms } ;
-
-HELP: with-basic-authentication
-{ $values
- { "realm" "a string" } { "quot" "a quotation with stack effect ( -- )" } }
-{ $description
- "Checks if the HTTP request has the correct authorisation headers "
- "for basic authentication within the named realm. If the headers "
- "are not present then a '401' HTTP response results from the "
- "request, otherwise the quotation is called." }
-{ $examples
-{ $code "\"my-realm\" [\n serving-html \"
Success!\" write\n] with-basic-authentication" } }
-{ $see-also add-realm realms }
- ;
-
-ARTICLE: { "http-authentication" "basic-authentication" } "Basic Authentication"
-"The Basic Authentication system provides a simple browser based "
-"authentication method to web applications. When the browser requests "
-"a resource protected with basic authentication the server responds with "
-"a '401' response code which means the user is unauthorized."
-$nl
-"When the browser receives this it prompts the user for a username and "
-"password. This is sent back to the server in a special HTTP header. The "
-"server then checks this against its authentication information and either "
-"accepts or rejects the users request."
-$nl
-"Authentication is split up into " { $link realms } ". Each realm can have "
-"a different database of username and password information. A responder can "
-"require basic authentication by using the " { $link with-basic-authentication } " word."
-$nl
-"Username and password information can be maintained using " { $link realms } " and " { $link add-realm } "."
-$nl
-"All passwords on the server should be stored as sha-256 strings generated with the " { $link string>sha-256-string } " word."
-$nl
-"Note that Basic Authentication itself is insecure in that it "
-"sends the username and password as clear text (although it is "
-"base64 encoded this is not much help). To prevent eavesdropping "
-"it is best to use Basic Authentication with SSL." ;
-
-IN: http.basic-authentication
-ABOUT: { "http-authentication" "basic-authentication" }
diff --git a/extra/http/basic-authentication/basic-authentication-tests.factor b/extra/http/basic-authentication/basic-authentication-tests.factor
deleted file mode 100644
index 318123b0b4..0000000000
--- a/extra/http/basic-authentication/basic-authentication-tests.factor
+++ /dev/null
@@ -1,66 +0,0 @@
-! Copyright (c) 2007 Chris Double.
-! See http://factorcode.org/license.txt for BSD license.
-USING: kernel crypto.sha2 http.basic-authentication tools.test
- namespaces base64 sequences ;
-
-{ t } [
- [
- H{ } clone realms set
- H{ { "admin" "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" } } "test-realm" add-realm
- "test-realm" "Basic " "admin:password" >base64 append authorization-ok?
- ] with-scope
-] unit-test
-
-{ f } [
- [
- H{ } clone realms set
- H{ { "admin" "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" } } "test-realm" add-realm
- "test-realm" "Basic " "admin:passwordx" >base64 append authorization-ok?
- ] with-scope
-] unit-test
-
-{ f } [
- [
- H{ } clone realms set
- H{ { "admin" "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" } } "test-realm" add-realm
- "test-realm" "Basic " "xadmin:password" >base64 append authorization-ok?
- ] with-scope
-] unit-test
-
-{ t } [
- [
- H{ } clone realms set
- [ "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" = swap "admin" = and ] "test-realm" add-realm
- "test-realm" "Basic " "admin:password" >base64 append authorization-ok?
- ] with-scope
-] unit-test
-
-{ f } [
- [
- H{ } clone realms set
- [ "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" = swap "admin" = and ] "test-realm" add-realm
- "test-realm" "Basic " "xadmin:password" >base64 append authorization-ok?
- ] with-scope
-] unit-test
-
-{ f } [
- [
- H{ } clone realms set
- [ "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" = swap "admin" = and ] "test-realm" add-realm
- "test-realm" "Basic " "admin:xpassword" >base64 append authorization-ok?
- ] with-scope
-] unit-test
-
-{ f } [
- [
- f realms set
- "test-realm" "Basic " "admin:password" >base64 append authorization-ok?
- ] with-scope
-] unit-test
-
-{ f } [
- [
- H{ } clone realms set
- "test-realm" "Basic " "admin:password" >base64 append authorization-ok?
- ] with-scope
-] unit-test
diff --git a/extra/http/basic-authentication/basic-authentication.factor b/extra/http/basic-authentication/basic-authentication.factor
deleted file mode 100644
index dfe04dc4b5..0000000000
--- a/extra/http/basic-authentication/basic-authentication.factor
+++ /dev/null
@@ -1,65 +0,0 @@
-! Copyright (c) 2007 Chris Double.
-! See http://factorcode.org/license.txt for BSD license.
-USING: kernel base64 http.server crypto.sha2 namespaces assocs
- quotations hashtables combinators splitting sequences
- http.server.responders io html.elements ;
-IN: http.basic-authentication
-
-! 'realms' is a hashtable mapping a realm (a string) to
-! either a quotation or a hashtable. The quotation
-! has stack effect ( username sha-256-string -- bool ).
-! It should perform the user authentication. 'sha-256-string'
-! is the plain text password provided by the user passed through
-! 'string>sha-256-string'. If 'realms' maps to a hashtable then
-! it is a mapping of usernames to sha-256 hashed passwords.
-!
-! 'realms' can be set on a per vhost basis in the vhosts
-! table.
-!
-! If there are no realms then authentication fails.
-SYMBOL: realms
-
-: add-realm ( data name -- )
- #! Add the named realm to the realms table.
- #! 'data' should be a hashtable or a quotation.
- realms get [ H{ } clone dup realms set ] unless*
- set-at ;
-
-: user-authorized? ( username password realm -- bool )
- realms get dup [
- at {
- { [ dup quotation? ] [ call ] }
- { [ dup hashtable? ] [ swapd at = ] }
- { [ t ] [ 3drop f ] }
- } cond
- ] [
- 3drop drop f
- ] if ;
-
-: authorization-ok? ( realm header -- bool )
- #! Given the realm and the 'Authorization' header,
- #! authenticate the user.
- dup [
- " " split dup first "Basic" = [
- second base64> ":" split first2 string>sha-256-string rot
- user-authorized?
- ] [
- 2drop f
- ] if
- ] [
- 2drop f
- ] if ;
-
-: authentication-error ( realm -- )
- "401 Unauthorized" response
- "Basic realm=\"" swap "\"" 3append "WWW-Authenticate" associate print-header
-
- "Username or Password is invalid" write
- ;
-
-: with-basic-authentication ( realm quot -- )
- #! Check if the user is authenticated in the given realm
- #! to run the specified quotation. If not, use Basic
- #! Authentication to ask for authorization details.
- over "authorization" header-param authorization-ok?
- [ nip call ] [ drop authentication-error ] if ;
diff --git a/extra/http/basic-authentication/summary.txt b/extra/http/basic-authentication/summary.txt
deleted file mode 100644
index 60cef7e630..0000000000
--- a/extra/http/basic-authentication/summary.txt
+++ /dev/null
@@ -1 +0,0 @@
-HTTP Basic Authentication implementation
diff --git a/extra/http/basic-authentication/tags.txt b/extra/http/basic-authentication/tags.txt
deleted file mode 100644
index c0772185a0..0000000000
--- a/extra/http/basic-authentication/tags.txt
+++ /dev/null
@@ -1 +0,0 @@
-web
diff --git a/extra/http/client/client-tests.factor b/extra/http/client/client-tests.factor
index 5e407657a8..4fca1697a5 100755
--- a/extra/http/client/client-tests.factor
+++ b/extra/http/client/client-tests.factor
@@ -16,6 +16,8 @@ tuple-syntax namespaces ;
host: "www.apple.com"
path: "/index.html"
port: 80
+ version: "1.1"
+ cookies: V{ }
}
] [
[
diff --git a/extra/http/client/client.factor b/extra/http/client/client.factor
index 8b74b6dc72..1c408e44e3 100755
--- a/extra/http/client/client.factor
+++ b/extra/http/client/client.factor
@@ -2,7 +2,7 @@
! See http://factorcode.org/license.txt for BSD license.
USING: assocs http kernel math math.parser namespaces sequences
io io.sockets io.streams.string io.files io.timeouts strings
-splitting continuations assocs.lib calendar vectors hashtables
+splitting continuations calendar vectors hashtables
accessors ;
IN: http.client
@@ -32,7 +32,7 @@ DEFER: (http-request)
: do-redirect ( response -- response stream )
dup response-code 300 399 between? [
- header>> "location" peek-at
+ header>> "location" swap at
dup "http://" head? [
absolute-redirect
] [
@@ -44,7 +44,7 @@ DEFER: (http-request)
: (http-request) ( request -- response stream )
dup host>> over port>> stdio set
- write-request flush read-response
+ dup "r" set-global write-request flush read-response
do-redirect ;
PRIVATE>
@@ -59,8 +59,7 @@ PRIVATE>
] with-scope ;
: ( -- request )
- request construct-empty
- "GET" >>method ;
+ "GET" >>method ;
: http-get-stream ( url -- response stream )
http-request ;
@@ -86,7 +85,7 @@ PRIVATE>
dup download-name download-to ;
: ( content-type content -- request )
- request construct-empty
+
"POST" >>method
swap >>post-data
swap >>post-data-type ;
diff --git a/extra/http/http-tests.factor b/extra/http/http-tests.factor
index 9fa593053c..681ebd97e2 100755
--- a/extra/http/http-tests.factor
+++ b/extra/http/http-tests.factor
@@ -29,12 +29,14 @@ blah
[
TUPLE{ request
+ port: 80
method: "GET"
path: "bar"
- query: f
+ query: H{ }
version: "1.1"
- header: H{ { "some-header" V{ "1" "2" } } { "content-length" V{ "4" } } }
+ header: H{ { "some-header" "1; 2" } { "content-length" "4" } }
post-data: "blah"
+ cookies: V{ }
}
] [
read-request-test-1 [
@@ -45,8 +47,7 @@ blah
STRING: read-request-test-1'
GET bar HTTP/1.1
content-length: 4
-some-header: 1
-some-header: 2
+some-header: 1; 2
blah
;
@@ -60,18 +61,20 @@ read-request-test-1' 1array [
] unit-test
STRING: read-request-test-2
-HEAD http://foo/bar HTTP/1.0
+HEAD http://foo/bar HTTP/1.1
Host: www.sex.com
;
[
TUPLE{ request
+ port: 80
method: "HEAD"
path: "bar"
- query: f
- version: "1.0"
- header: H{ { "host" V{ "www.sex.com" } } }
+ query: H{ }
+ version: "1.1"
+ header: H{ { "host" "www.sex.com" } }
host: "www.sex.com"
+ cookies: V{ }
}
] [
read-request-test-2 [
@@ -80,7 +83,7 @@ Host: www.sex.com
] unit-test
STRING: read-response-test-1
-HTTP/1.0 404 not found
+HTTP/1.1 404 not found
Content-Type: text/html
blah
@@ -88,10 +91,11 @@ blah
[
TUPLE{ response
- version: "1.0"
+ version: "1.1"
code: 404
message: "not found"
- header: H{ { "content-type" V{ "text/html" } } }
+ header: H{ { "content-type" "text/html" } }
+ cookies: V{ }
}
] [
read-response-test-1
@@ -100,7 +104,7 @@ blah
STRING: read-response-test-1'
-HTTP/1.0 404 not found
+HTTP/1.1 404 not found
content-type: text/html
@@ -113,3 +117,8 @@ read-response-test-1' 1array [
! normalize crlf
string-lines "\n" join
] unit-test
+
+[ t ] [
+ "rmid=732423sdfs73242; path=/; domain=.example.net; expires=Fri, 31-Dec-2010 23:59:59 GMT"
+ dup parse-cookies unparse-cookies =
+] unit-test
diff --git a/extra/http/http.factor b/extra/http/http.factor
index 4c2834b7ca..8686d87052 100755
--- a/extra/http/http.factor
+++ b/extra/http/http.factor
@@ -2,34 +2,13 @@
! See http://factorcode.org/license.txt for BSD license.
USING: hashtables io io.streams.string kernel math namespaces
math.parser assocs sequences strings splitting ascii
-io.encodings.utf8 assocs.lib namespaces unicode.case combinators
-vectors sorting new-slots accessors calendar ;
+io.encodings.utf8 namespaces unicode.case combinators
+vectors sorting new-slots accessors calendar calendar.format
+quotations arrays ;
IN: http
: http-port 80 ; inline
-: crlf "\r\n" write ;
-
-: header-line ( line -- )
- ": " split1 dup [ swap >lower insert ] [ 2drop ] if ;
-
-: read-header-line ( -- )
- readln dup
- empty? [ drop ] [ header-line read-header-line ] if ;
-
-: read-header ( -- multi-assoc )
- [ read-header-line ] H{ } make-assoc ;
-
-: write-header ( multi-assoc -- )
- >alist sort-keys
- [
- swap write ": " write {
- { [ dup number? ] [ number>string ] }
- { [ dup timestamp? ] [ timestamp>http-string ] }
- { [ dup string? ] [ ] }
- } cond write crlf
- ] multi-assoc-each crlf ;
-
: url-quotable? ( ch -- ? )
#! In a URL, can this character be used without
#! URL-encoding?
@@ -73,6 +52,54 @@ IN: http
: url-decode ( str -- str )
[ 0 swap url-decode-iter ] "" make decode-utf8 ;
+: crlf "\r\n" write ;
+
+: add-header ( value key assoc -- )
+ [ at dup [ "; " rot 3append ] [ drop ] if ] 2keep set-at ;
+
+: header-line ( line -- )
+ dup first blank? [
+ [ blank? ] left-trim
+ "last-header" get
+ "header" get
+ add-header
+ ] [
+ ": " split1 dup [
+ swap >lower dup "last-header" set
+ "header" get add-header
+ ] [
+ 2drop
+ ] if
+ ] if ;
+
+: read-header-line ( -- )
+ readln dup
+ empty? [ drop ] [ header-line read-header-line ] if ;
+
+: read-header ( -- assoc )
+ H{ } clone [
+ "header" [ read-header-line ] with-variable
+ ] keep ;
+
+: header-value>string ( value -- string )
+ {
+ { [ dup number? ] [ number>string ] }
+ { [ dup timestamp? ] [ timestamp>http-string ] }
+ { [ dup string? ] [ ] }
+ { [ dup sequence? ] [ [ header-value>string ] map "; " join ] }
+ } cond ;
+
+: check-header-string ( str -- str )
+ #! http://en.wikipedia.org/wiki/HTTP_Header_Injection
+ dup [ "\r\n" member? ] contains?
+ [ "Header injection attack" throw ] when ;
+
+: write-header ( assoc -- )
+ >alist sort-keys [
+ swap url-encode write ": " write
+ header-value>string check-header-string write crlf
+ ] assoc-each crlf ;
+
: query>assoc ( query -- assoc )
dup [
"&" split [
@@ -84,6 +111,50 @@ IN: http
[ [ url-encode ] 2apply "=" swap 3append ] { } assoc>map
"&" join ;
+TUPLE: cookie name value path domain expires http-only ;
+
+: ( value name -- cookie )
+ cookie construct-empty
+ swap >>name swap >>value ;
+
+: parse-cookies ( string -- seq )
+ [
+ f swap
+
+ ";" split [
+ [ blank? ] trim "=" split1 swap >lower {
+ { "expires" [ >>expires ] }
+ { "domain" [ >>domain ] }
+ { "path" [ >>path ] }
+ { "httponly" [ drop t >>http-only ] }
+ { "" [ drop ] }
+ [ dup , nip ]
+ } case
+ ] each
+
+ drop
+ ] { } make ;
+
+: (unparse-cookie) ( key value -- )
+ {
+ { [ dup f eq? ] [ 2drop ] }
+ { [ dup t eq? ] [ drop , ] }
+ { [ t ] [ "=" swap 3append , ] }
+ } cond ;
+
+: unparse-cookie ( cookie -- strings )
+ [
+ dup name>> >lower over value>> (unparse-cookie)
+ "path" over path>> (unparse-cookie)
+ "domain" over domain>> (unparse-cookie)
+ "expires" over expires>> (unparse-cookie)
+ "httponly" over http-only>> (unparse-cookie)
+ drop
+ ] { } make ;
+
+: unparse-cookies ( cookies -- string )
+ [ unparse-cookie ] map concat "; " join ;
+
TUPLE: request
host
port
@@ -93,12 +164,21 @@ query
version
header
post-data
-post-data-type ;
+post-data-type
+cookies ;
:
request construct-empty
- "1.0" >>version
- http-port >>port ;
+ "1.1" >>version
+ http-port >>port
+ H{ } clone >>query
+ V{ } clone >>cookies ;
+
+: query-param ( request key -- value )
+ swap query>> at ;
+
+: set-query-param ( request value key -- request )
+ pick query>> set-at ;
: url>path ( url -- path )
url-decode "http://" ?head
@@ -132,12 +212,15 @@ post-data-type ;
: read-request-header ( request -- request )
read-header >>header ;
+: header ( request/response key -- value )
+ swap header>> at ;
+
SYMBOL: max-post-request
1024 256 * max-post-request set-global
: content-length ( header -- n )
- "content-length" peek-at string>number dup [
+ "content-length" swap at string>number dup [
dup max-post-request get > [
"content-length > max-post-request" throw
] when
@@ -151,10 +234,13 @@ SYMBOL: max-post-request
[ string>number ] [ http-port ] if* ;
: extract-host ( request -- request )
- dup header>> "host" peek-at parse-host >r >>host r> >>port ;
+ dup "host" header parse-host >r >>host r> >>port ;
: extract-post-data-type ( request -- request )
- dup header>> "content-type" peek-at >>post-data-type ;
+ dup "content-type" header >>post-data-type ;
+
+: extract-cookies ( request -- request )
+ dup "cookie" header [ parse-cookies >>cookies ] when* ;
: read-request ( -- request )
@@ -164,7 +250,8 @@ SYMBOL: max-post-request
read-request-header
read-post-data
extract-host
- extract-post-data-type ;
+ extract-post-data-type
+ extract-cookies ;
: write-method ( request -- request )
dup method>> write bl ;
@@ -184,9 +271,10 @@ SYMBOL: max-post-request
: write-request-header ( request -- request )
dup header>> >hashtable
- over host>> [ "host" replace-at ] when*
- over post-data>> [ length "content-length" replace-at ] when*
- over post-data-type>> [ "content-type" replace-at ] when*
+ over host>> [ "host" pick set-at ] when*
+ over post-data>> [ length "content-length" pick set-at ] when*
+ over post-data-type>> [ "content-type" pick set-at ] when*
+ over cookies>> f like [ unparse-cookies "cookie" pick set-at ] when*
write-header ;
: write-post-data ( request -- request )
@@ -194,7 +282,7 @@ SYMBOL: max-post-request
: write-request ( request -- )
write-method
- write-url
+ write-request-url
write-version
write-request-header
write-post-data
@@ -209,30 +297,42 @@ SYMBOL: max-post-request
":" write
dup port>> number>string write
] when
- "/" write
+ dup path>> "/" head? [ "/" write ] unless
write-url
drop
] with-string-writer ;
+: set-header ( request/response value key -- request/response )
+ pick header>> set-at ;
+
+GENERIC: write-response ( response -- )
+
+GENERIC: write-full-response ( request response -- )
+
TUPLE: response
version
code
message
-header ;
+header
+cookies
+body ;
:
response construct-empty
- "1.0" >>version
- H{ } clone >>header ;
+ "1.1" >>version
+ H{ } clone >>header
+ "close" "connection" set-header
+ now timestamp>http-string "date" set-header
+ V{ } clone >>cookies ;
: read-response-version
- " " read-until
+ " \t" read-until
[ "Bad response: version" throw ] unless
parse-version
>>version ;
: read-response-code
- " " read-until [ "Bad response: code" throw ] unless
+ " \t" read-until [ "Bad response: code" throw ] unless
string>number [ "Bad response: code" throw ] unless*
>>code ;
@@ -240,7 +340,8 @@ header ;
readln >>message ;
: read-response-header
- read-header >>header ;
+ read-header >>header
+ dup "set-cookie" header [ parse-cookies >>cookies ] when* ;
: read-response ( -- response )
@@ -260,9 +361,20 @@ header ;
dup message>> write crlf ;
: write-response-header ( response -- response )
- dup header>> write-header ;
+ dup header>> clone
+ over cookies>> f like
+ [ unparse-cookies "set-cookie" pick set-at ] when*
+ write-header ;
-: write-response ( respose -- )
+: write-response-body ( response -- response )
+ dup body>> {
+ { [ dup not ] [ drop ] }
+ { [ dup string? ] [ write ] }
+ { [ dup callable? ] [ call ] }
+ { [ t ] [ stdio get stream-copy ] }
+ } cond ;
+
+M: response write-response ( respose -- )
write-response-version
write-response-code
write-response-message
@@ -270,8 +382,39 @@ header ;
flush
drop ;
-: set-response-header ( response value key -- response )
- pick header>> -rot replace-at drop ;
+M: response write-full-response ( request response -- )
+ dup write-response
+ swap method>> "HEAD" = [ write-response-body ] unless ;
-: set-content-type ( response content-type -- response )
- "content-type" set-response-header ;
+: set-content-type ( request/response content-type -- request/response )
+ "content-type" set-header ;
+
+: get-cookie ( request/response name -- cookie/f )
+ >r cookies>> r> [ swap name>> = ] curry find nip ;
+
+: delete-cookie ( request/response name -- )
+ over cookies>> >r get-cookie r> delete ;
+
+: put-cookie ( request/response cookie -- request/response )
+ [ dupd name>> get-cookie [ dupd delete-cookie ] when* ] keep
+ over cookies>> push ;
+
+TUPLE: raw-response
+version
+code
+message
+body ;
+
+: ( -- response )
+ raw-response construct-empty
+ "1.1" >>version ;
+
+M: raw-response write-response ( respose -- )
+ write-response-version
+ write-response-code
+ write-response-message
+ write-response-body
+ drop ;
+
+M: raw-response write-full-response ( response -- )
+ write-response nip ;
diff --git a/extra/http/mime/mime.factor b/extra/http/mime/mime.factor
old mode 100644
new mode 100755
index 3365127d87..f9097ecce3
--- a/extra/http/mime/mime.factor
+++ b/extra/http/mime/mime.factor
@@ -30,5 +30,6 @@ H{
{ "pdf" "application/pdf" }
{ "factor" "text/plain" }
+ { "cgi" "application/x-cgi-script" }
{ "fhtml" "application/x-factor-server-page" }
} "mime-types" set-global
diff --git a/extra/http/server/authentication/basic/basic.factor b/extra/http/server/authentication/basic/basic.factor
new file mode 100755
index 0000000000..b6dbed4b62
--- /dev/null
+++ b/extra/http/server/authentication/basic/basic.factor
@@ -0,0 +1,50 @@
+! Copyright (c) 2007 Chris Double.
+! See http://factorcode.org/license.txt for BSD license.
+IN: http.server.authentication.basic
+USING: accessors new-slots quotations assocs kernel splitting
+base64 crypto.sha2 html.elements io combinators http.server
+http sequences ;
+
+! 'users' is a quotation or an assoc. The quotation
+! has stack effect ( sha-256-string username -- ? ).
+! It should perform the user authentication. 'sha-256-string'
+! is the plain text password provided by the user passed through
+! 'string>sha-256-string'. If 'users' is an assoc then
+! it is a mapping of usernames to sha-256 hashed passwords.
+TUPLE: realm responder name users ;
+
+C: realm
+
+: user-authorized? ( password username realm -- ? )
+ users>> {
+ { [ dup callable? ] [ call ] }
+ { [ dup assoc? ] [ at = ] }
+ } cond ;
+
+: authorization-ok? ( realm header -- bool )
+ #! Given the realm and the 'Authorization' header,
+ #! authenticate the user.
+ dup [
+ " " split1 swap "Basic" = [
+ base64> ":" split1 string>sha-256-string
+ spin user-authorized?
+ ] [
+ 2drop f
+ ] if
+ ] [
+ 2drop f
+ ] if ;
+
+: <401> ( realm -- response )
+ 401 "Unauthorized"
+ "Basic realm=\"" rot name>> "\"" 3append
+ "WWW-Authenticate" set-header
+ [
+
+ "Username or Password is invalid" write
+
+ ] >>body ;
+
+M: realm call-responder ( request path realm -- response )
+ pick "authorization" header dupd authorization-ok?
+ [ responder>> call-responder ] [ 2nip <401> ] if ;
diff --git a/extra/http/server/callbacks/callbacks.factor b/extra/http/server/callbacks/callbacks.factor
new file mode 100755
index 0000000000..a000a76040
--- /dev/null
+++ b/extra/http/server/callbacks/callbacks.factor
@@ -0,0 +1,170 @@
+! Copyright (C) 2004 Chris Double.
+! Copyright (C) 2006, 2008 Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+USING: html http http.server io kernel math namespaces
+continuations calendar sequences assocs new-slots hashtables
+accessors arrays alarms quotations combinators ;
+IN: http.server.callbacks
+
+SYMBOL: responder
+
+TUPLE: callback-responder responder callbacks ;
+
+: ( responder -- responder' )
+ #! A continuation responder is a special type of session
+ #! manager. However it works entirely differently from
+ #! the URL and cookie session managers.
+ H{ } clone callback-responder construct-boa ;
+
+TUPLE: callback cont quot expires alarm responder ;
+
+: timeout 20 minutes ;
+
+: timeout-callback ( callback -- )
+ dup alarm>> cancel-alarm
+ dup responder>> callbacks>> delete-at ;
+
+: touch-callback ( callback -- )
+ dup expires>> [
+ dup alarm>> [ cancel-alarm ] when*
+ dup [ timeout-callback ] curry timeout later >>alarm
+ ] when drop ;
+
+: ( cont quot expires? -- callback )
+ [ f responder get callback construct-boa ] keep
+ [ dup touch-callback ] when ;
+
+: invoke-callback ( request exit-cont callback -- response )
+ [ quot>> 3array ] keep cont>> continue-with ;
+
+: register-callback ( cont quot expires? -- id )
+
+ responder get callbacks>> generate-key
+ [ responder get callbacks>> set-at ] keep ;
+
+SYMBOL: exit-continuation
+
+: exit-with exit-continuation get continue-with ;
+
+: forward-to-url ( url -- * )
+ #! When executed inside a 'show' call, this will force a
+ #! HTTP 302 to occur to instruct the browser to forward to
+ #! the request URL.
+ exit-with ;
+
+: cont-id "factorcontid" ;
+
+: id>url ( id -- url )
+ request get clone
+ swap cont-id associate >>query
+ request-url ;
+
+: forward-to-id ( id -- * )
+ #! When executed inside a 'show' call, this will force a
+ #! HTTP 302 to occur to instruct the browser to forward to
+ #! the request URL.
+ id>url forward-to-url ;
+
+: restore-request ( pair -- )
+ first3 >r exit-continuation set request set r> call ;
+
+: resume-page ( request page responder callback -- * )
+ dup touch-callback
+ >r 2drop exit-continuation get
+ r> invoke-callback ;
+
+SYMBOL: post-refresh-get?
+
+: redirect-to-here ( -- )
+ #! Force a redirect to the client browser so that the browser
+ #! goes to the current point in the code. This forces an URL
+ #! change on the browser so that refreshing that URL will
+ #! immediately run from this code point. This prevents the
+ #! "this request will issue a POST" warning from the browser
+ #! and prevents re-running the previous POST logic. This is
+ #! known as the 'post-refresh-get' pattern.
+ post-refresh-get? get [
+ [
+ [ ] t register-callback forward-to-id
+ ] callcc1 restore-request
+ ] [
+ post-refresh-get? on
+ ] if ;
+
+SYMBOL: current-show
+
+: store-current-show ( -- )
+ #! Store the current continuation in the variable 'current-show'
+ #! so it can be returned to later by 'quot-id'. Note that it
+ #! recalls itself when the continuation is called to ensure that
+ #! it resets its value back to the most recent show call.
+ [ current-show set f ] callcc1
+ [ restore-request store-current-show ] when* ;
+
+: show-final ( quot -- * )
+ [
+ >r store-current-show redirect-to-here r> call exit-with
+ ] with-scope ; inline
+
+M: callback-responder call-responder
+ [
+ [
+ exit-continuation set
+ dup responder set
+ pick request set
+ pick cont-id query-param over callbacks>> at [
+ resume-page
+ ] [
+ responder>> call-responder
+ "Continuation responder pages must use show-final" throw
+ ] if*
+ ] with-scope
+ ] callcc1 >r 3drop r> ;
+
+: show-page ( quot -- )
+ [
+ >r store-current-show redirect-to-here r>
+ [
+ [ ] register-callback
+ call
+ exit-with
+ ] callcc1 restore-request
+ ] with-scope ; inline
+
+: quot-id ( quot -- id )
+ current-show get swap t register-callback ;
+
+: quot-url ( quot -- url )
+ quot-id id>url ;
+
+! SYMBOL: current-show
+!
+! : store-current-show ( -- )
+! #! Store the current continuation in the variable 'current-show'
+! #! so it can be returned to later by href callbacks. Note that it
+! #! recalls itself when the continuation is called to ensure that
+! #! it resets its value back to the most recent show call.
+! [ ( 0 -- )
+! [ ( 0 1 -- )
+! current-show set ( 0 -- )
+! continue
+! ] callcc1
+! nip
+! store-current-show
+! ] callcc0 ;
+!
+
+!
+! : show-final ( quot -- * )
+! store-current-show
+! redirect-to-here
+! call
+! exit-with ; inline
+!
+! : show-page ( quot -- request )
+! store-current-show redirect-to-here
+! [
+! register-continuation
+! call
+! exit-with
+! ] callcc1 restore-request ; inline
diff --git a/extra/http/server/cgi/cgi.factor b/extra/http/server/cgi/cgi.factor
new file mode 100755
index 0000000000..9950a9a4a4
--- /dev/null
+++ b/extra/http/server/cgi/cgi.factor
@@ -0,0 +1,65 @@
+! Copyright (C) 2007, 2008 Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+USING: namespaces kernel assocs io.files combinators
+arrays io.launcher io http.server.static http.server
+http accessors sequences strings math.parser ;
+IN: http.server.cgi
+
+: post? request get method>> "POST" = ;
+
+: cgi-variables ( script-path -- assoc )
+ #! This needs some work.
+ [
+ "CGI/1.0" "GATEWAY_INTERFACE" set
+ "HTTP/" request get version>> append "SERVER_PROTOCOL" set
+ "Factor" "SERVER_SOFTWARE" set
+
+ dup "PATH_TRANSLATED" set
+ "SCRIPT_FILENAME" set
+
+ request get path>> "SCRIPT_NAME" set
+
+ request get host>> "SERVER_NAME" set
+ request get port>> number>string "SERVER_PORT" set
+ "" "PATH_INFO" set
+ "" "REMOTE_HOST" set
+ "" "REMOTE_ADDR" set
+ "" "AUTH_TYPE" set
+ "" "REMOTE_USER" set
+ "" "REMOTE_IDENT" set
+
+ request get method>> "REQUEST_METHOD" set
+ request get query>> assoc>query "QUERY_STRING" set
+ request get "cookie" header "HTTP_COOKIE" set
+
+ request get "user-agent" header "HTTP_USER_AGENT" set
+ request get "accept" header "HTTP_ACCEPT" set
+
+ post? [
+ request get post-data-type>> "CONTENT_TYPE" set
+ request get post-data>> length number>string "CONTENT_LENGTH" set
+ ] when
+ ] H{ } make-assoc ;
+
+: cgi-descriptor ( name -- desc )
+ [
+ dup 1array +arguments+ set
+ cgi-variables +environment+ set
+ ] H{ } make-assoc ;
+
+: serve-cgi ( name -- response )
+
+ 200 >>code
+ "CGI output follows" >>message
+ swap [
+ stdio get swap cgi-descriptor [
+ post? [
+ request get post-data>> write flush
+ ] when
+ stdio get swap (stream-copy)
+ ] with-stream
+ ] curry >>body ;
+
+: enable-cgi ( responder -- responder )
+ [ serve-cgi ] "application/x-cgi-script"
+ pick special>> set-at ;
diff --git a/extra/http/server/server-tests.factor b/extra/http/server/server-tests.factor
index a67d21a640..8616071580 100755
--- a/extra/http/server/server-tests.factor
+++ b/extra/http/server/server-tests.factor
@@ -1,45 +1,53 @@
USING: http.server tools.test kernel namespaces accessors
-new-slots assocs.lib io http math sequences ;
+new-slots io http math sequences assocs ;
IN: temporary
-TUPLE: mock-responder ;
+TUPLE: mock-responder path ;
-: ( path -- responder )
- mock-responder construct-delegate ;
+C: mock-responder
-M: mock-responder do-responder
+M: mock-responder call-responder
2nip
path>> on
- [ "Hello world" print ]
"text/plain" ;
: check-dispatch ( tag path -- ? )
over off
swap default-host get call-responder
- write-response call get ;
+ write-response get ;
[
- ""
- "foo" add-responder
- "bar" add-responder
- "baz/"
- "123" add-responder
+
+ "foo" "foo" add-responder
+ "bar" "bar" add-responder
+
+ "123" "123" add-responder
"default" >>default
- add-responder
+ "baz" add-responder
default-host set
+ [ "foo" ] [
+ "foo" default-host get find-responder path>> nip
+ ] unit-test
+
+ [ "bar" ] [
+ "bar" default-host get find-responder path>> nip
+ ] unit-test
+
[ t ] [ "foo" "foo" check-dispatch ] unit-test
[ f ] [ "foo" "bar" check-dispatch ] unit-test
[ t ] [ "bar" "bar" check-dispatch ] unit-test
[ t ] [ "default" "baz/xxx" check-dispatch ] unit-test
+ [ t ] [ "default" "baz/xxx//" check-dispatch ] unit-test
+ [ t ] [ "default" "/baz/xxx//" check-dispatch ] unit-test
[ t ] [ "123" "baz/123" check-dispatch ] unit-test
+ [ t ] [ "123" "baz///123" check-dispatch ] unit-test
[ t ] [
"baz" >>path
"baz" default-host get call-responder
dup code>> 300 399 between? >r
- header>> "location" peek-at "baz/" tail? r> and
- nip
+ header>> "location" swap at "baz/" tail? r> and
] unit-test
] with-scope
diff --git a/extra/http/server/server.factor b/extra/http/server/server.factor
index e06ae6a95c..3780b2110d 100755
--- a/extra/http/server/server.factor
+++ b/extra/http/server/server.factor
@@ -2,24 +2,17 @@
! See http://factorcode.org/license.txt for BSD license.
USING: assocs kernel namespaces io io.timeouts strings splitting
threads http sequences prettyprint io.server logging calendar
-new-slots html.elements accessors math.parser combinators.lib ;
+new-slots html.elements accessors math.parser combinators.lib
+vocabs.loader debugger html continuations random ;
IN: http.server
-TUPLE: responder path directory ;
+GENERIC: call-responder ( request path responder -- response )
-: ( path -- responder )
- "/" ?tail responder construct-boa ;
+TUPLE: trivial-responder response ;
-GENERIC: do-responder ( request path responder -- quot response )
+C: trivial-responder
-TUPLE: trivial-responder quot response ;
-
-: ( quot response -- responder )
- trivial-responder construct-boa
- "" over set-delegate ;
-
-M: trivial-responder do-responder
- 2nip dup quot>> swap response>> ;
+M: trivial-responder call-responder 2nip response>> call ;
: trivial-response-body ( code message -- )
@@ -28,23 +21,30 @@ M: trivial-responder do-responder