]> gitweb.factorcode.org Git - factor.git/commitdiff
Web framework refactoring work in progress
authorSlava Pestov <slava@slava-pestovs-macbook-pro.local>
Sun, 1 Jun 2008 22:22:39 +0000 (17:22 -0500)
committerSlava Pestov <slava@slava-pestovs-macbook-pro.local>
Sun, 1 Jun 2008 22:22:39 +0000 (17:22 -0500)
112 files changed:
extra/furnace/actions/actions-tests.factor [new file with mode: 0755]
extra/furnace/actions/actions.factor [new file with mode: 0755]
extra/furnace/auth/auth.factor [new file with mode: 0755]
extra/furnace/auth/basic/basic.factor [new file with mode: 0755]
extra/furnace/auth/login/boilerplate.xml [new file with mode: 0644]
extra/furnace/auth/login/edit-profile.xml [new file with mode: 0644]
extra/furnace/auth/login/login-tests.factor [new file with mode: 0755]
extra/furnace/auth/login/login.factor [new file with mode: 0755]
extra/furnace/auth/login/login.xml [new file with mode: 0644]
extra/furnace/auth/login/recover-1.xml [new file with mode: 0644]
extra/furnace/auth/login/recover-2.xml [new file with mode: 0644]
extra/furnace/auth/login/recover-3.xml [new file with mode: 0644]
extra/furnace/auth/login/recover-4.xml [new file with mode: 0755]
extra/furnace/auth/login/register.xml [new file with mode: 0644]
extra/furnace/auth/providers/assoc/assoc-tests.factor [new file with mode: 0755]
extra/furnace/auth/providers/assoc/assoc.factor [new file with mode: 0755]
extra/furnace/auth/providers/db/db-tests.factor [new file with mode: 0755]
extra/furnace/auth/providers/db/db.factor [new file with mode: 0755]
extra/furnace/auth/providers/null/null.factor [new file with mode: 0755]
extra/furnace/auth/providers/providers.factor [new file with mode: 0755]
extra/furnace/boilerplate/boilerplate.factor [new file with mode: 0644]
extra/furnace/callbacks/callbacks-tests.factor [new file with mode: 0755]
extra/furnace/callbacks/callbacks.factor [new file with mode: 0755]
extra/furnace/db/db-tests.factor [new file with mode: 0644]
extra/furnace/db/db.factor [new file with mode: 0755]
extra/furnace/flows/flows.factor [new file with mode: 0644]
extra/furnace/furnace.factor [new file with mode: 0644]
extra/furnace/sessions/authors.txt [new file with mode: 0755]
extra/furnace/sessions/sessions-tests.factor [new file with mode: 0755]
extra/furnace/sessions/sessions.factor [new file with mode: 0755]
extra/html/components/components-tests.factor
extra/html/components/components.factor
extra/html/elements/elements.factor
extra/html/templates/chloe/chloe-tests.factor
extra/html/templates/chloe/chloe.factor
extra/html/templates/chloe/syntax/syntax.factor [new file with mode: 0644]
extra/html/templates/chloe/test/test10.xml
extra/html/templates/chloe/test/test11.xml
extra/html/templates/chloe/test/test9.xml
extra/html/templates/templates.factor
extra/http/client/client-tests.factor
extra/http/client/client.factor
extra/http/http-tests.factor
extra/http/http.factor
extra/http/server/actions/actions-tests.factor [deleted file]
extra/http/server/actions/actions.factor [deleted file]
extra/http/server/auth/auth.factor [deleted file]
extra/http/server/auth/basic/basic.factor [deleted file]
extra/http/server/auth/login/boilerplate.xml [deleted file]
extra/http/server/auth/login/edit-profile.xml [deleted file]
extra/http/server/auth/login/login-tests.factor [deleted file]
extra/http/server/auth/login/login.factor [deleted file]
extra/http/server/auth/login/login.xml [deleted file]
extra/http/server/auth/login/recover-1.xml [deleted file]
extra/http/server/auth/login/recover-2.xml [deleted file]
extra/http/server/auth/login/recover-3.xml [deleted file]
extra/http/server/auth/login/recover-4.xml [deleted file]
extra/http/server/auth/login/register.xml [deleted file]
extra/http/server/auth/providers/assoc/assoc-tests.factor [deleted file]
extra/http/server/auth/providers/assoc/assoc.factor [deleted file]
extra/http/server/auth/providers/db/db-tests.factor [deleted file]
extra/http/server/auth/providers/db/db.factor [deleted file]
extra/http/server/auth/providers/null/null.factor [deleted file]
extra/http/server/auth/providers/providers.factor [deleted file]
extra/http/server/boilerplate/boilerplate.factor [deleted file]
extra/http/server/callbacks/callbacks-tests.factor [deleted file]
extra/http/server/callbacks/callbacks.factor [deleted file]
extra/http/server/cgi/cgi.factor
extra/http/server/db/db-tests.factor [deleted file]
extra/http/server/db/db.factor [deleted file]
extra/http/server/flows/flows.factor [deleted file]
extra/http/server/server-tests.factor
extra/http/server/server.factor
extra/http/server/sessions/authors.txt [deleted file]
extra/http/server/sessions/sessions-tests.factor [deleted file]
extra/http/server/sessions/sessions.factor [deleted file]
extra/http/server/static/static.factor
extra/io/pools/pools.factor
extra/lcs/diff2html/diff2html.factor
extra/rss/rss.factor
extra/tangle/tangle.factor
extra/urls/urls-tests.factor
extra/urls/urls.factor
extra/webapps/counter/counter.factor
extra/webapps/factor-website/factor-website.factor
extra/webapps/factor-website/page.xml
extra/webapps/pastebin/paste.xml
extra/webapps/pastebin/pastebin-common.xml
extra/webapps/pastebin/pastebin.factor
extra/webapps/pastebin/pastebin.xml
extra/webapps/planet/admin.xml
extra/webapps/planet/mini-planet.xml
extra/webapps/planet/planet.factor
extra/webapps/planet/planet.xml
extra/webapps/todo/edit-todo.xml
extra/webapps/todo/new-todo.xml [new file with mode: 0644]
extra/webapps/todo/todo-list.xml
extra/webapps/todo/todo.factor
extra/webapps/todo/todo.xml
extra/webapps/user-admin/edit-user.xml
extra/webapps/user-admin/new-user.xml
extra/webapps/user-admin/user-admin.factor
extra/webapps/user-admin/user-list.xml
extra/webapps/wiki/articles.xml
extra/webapps/wiki/changes.xml
extra/webapps/wiki/diff.xml
extra/webapps/wiki/revisions.xml
extra/webapps/wiki/user-edits.xml
extra/webapps/wiki/wiki.css
extra/webapps/wiki/wiki.factor
extra/xmode/code2html/code2html.factor
extra/xmode/code2html/responder/responder.factor

diff --git a/extra/furnace/actions/actions-tests.factor b/extra/furnace/actions/actions-tests.factor
new file mode 100755 (executable)
index 0000000..8aa0f92
--- /dev/null
@@ -0,0 +1,23 @@
+USING: kernel furnace.actions validators
+tools.test math math.parser multiline namespaces http
+io.streams.string http.server sequences splitting accessors ;
+IN: furnace.actions.tests
+
+<action>
+    [ "a" param "b" param [ string>number ] bi@ + ] >>display
+"action-1" set
+
+: lf>crlf "\n" split "\r\n" join ;
+
+STRING: action-request-test-1
+GET http://foo/bar?a=12&b=13 HTTP/1.1
+
+blah
+;
+
+[ 25 ] [
+    action-request-test-1 lf>crlf
+    [ read-request ] with-string-reader
+    init-request
+    { } "action-1" get call-responder
+] unit-test
diff --git a/extra/furnace/actions/actions.factor b/extra/furnace/actions/actions.factor
new file mode 100755 (executable)
index 0000000..26042d6
--- /dev/null
@@ -0,0 +1,100 @@
+! Copyright (C) 2008 Slava Pestov.\r
+! See http://factorcode.org/license.txt for BSD license.\r
+USING: accessors sequences kernel assocs combinators http.server\r
+validators http hashtables namespaces fry continuations locals\r
+boxes xml.entities html.elements html.components\r
+html.templates.chloe io arrays math ;\r
+IN: furnace.actions\r
+\r
+SYMBOL: params\r
+\r
+SYMBOL: rest-param\r
+\r
+: render-validation-messages ( -- )\r
+    validation-messages get\r
+    dup empty? [ drop ] [\r
+        <ul "errors" =class ul>\r
+            [ <li> message>> escape-string write </li> ] each\r
+        </ul>\r
+    ] if ;\r
+\r
+CHLOE: validation-messages drop render-validation-messages ;\r
+\r
+TUPLE: action rest-param init display validate submit ;\r
+\r
+: new-action ( class -- action )\r
+    new\r
+        [ ] >>init\r
+        [ <400> ] >>display\r
+        [ ] >>validate\r
+        [ <400> ] >>submit ;\r
+\r
+: <action> ( -- action )\r
+    action new-action ;\r
+\r
+: handle-get ( action -- response )\r
+    blank-values\r
+    [ init>> call ]\r
+    [ display>> call ]\r
+    bi ;\r
+\r
+: validation-failed ( -- * )\r
+    request get method>> "POST" =\r
+    [ action get display>> call ] [ <400> ] if exit-with ;\r
+\r
+: handle-post ( action -- response )\r
+    init-validation\r
+    blank-values\r
+    [ validate>> call ]\r
+    [ submit>> call ] bi ;\r
+\r
+: handle-rest-param ( arg -- )\r
+    dup length 1 > action get rest-param>> not or\r
+    [ <404> exit-with ] [\r
+        action get rest-param>> associate rest-param set\r
+    ] if ;\r
+\r
+M: action call-responder* ( path action -- response )\r
+    dup action set\r
+    '[\r
+        , dup empty? [ drop ] [ handle-rest-param ] if\r
+\r
+        init-validation\r
+        ,\r
+        request get\r
+        [ request-params rest-param get assoc-union params set ]\r
+        [ method>> ] bi\r
+        {\r
+            { "GET" [ handle-get ] }\r
+            { "HEAD" [ handle-get ] }\r
+            { "POST" [ handle-post ] }\r
+        } case\r
+    ] with-exit-continuation ;\r
+\r
+: param ( name -- value )\r
+    params get at ;\r
+\r
+: check-validation ( -- )\r
+    validation-failed? [ validation-failed ] when ;\r
+\r
+: validate-params ( validators -- )\r
+    params get swap validate-values from-object\r
+    check-validation ;\r
+\r
+: validate-integer-id ( -- )\r
+    { { "id" [ v-number ] } } validate-params ;\r
+\r
+TUPLE: page-action < action template ;\r
+\r
+: <chloe-content> ( path -- response )\r
+    resolve-template-path <chloe> "text/html" <content> ;\r
+\r
+: <page-action> ( -- page )\r
+    page-action new-action\r
+        dup '[ , template>> <chloe-content> ] >>display ;\r
+\r
+TUPLE: feed-action < action feed ;\r
+\r
+: <feed-action> ( -- feed )\r
+    feed-action new-action\r
+        dup '[ , feed>> call <feed-content> ] >>display ;\r
diff --git a/extra/furnace/auth/auth.factor b/extra/furnace/auth/auth.factor
new file mode 100755 (executable)
index 0000000..c42b73b
--- /dev/null
@@ -0,0 +1,41 @@
+! Copyright (c) 2008 Slava Pestov\r
+! See http://factorcode.org/license.txt for BSD license.\r
+USING: accessors assocs namespaces kernel sequences sets\r
+http.server\r
+furnace.sessions\r
+furnace.auth.providers ;\r
+IN: furnace.auth\r
+\r
+SYMBOL: logged-in-user\r
+\r
+GENERIC: init-user-profile ( responder -- )\r
+\r
+M: object init-user-profile drop ;\r
+\r
+M: dispatcher init-user-profile\r
+    default>> init-user-profile ;\r
+\r
+M: filter-responder init-user-profile\r
+    responder>> init-user-profile ;\r
+\r
+: profile ( -- assoc ) logged-in-user get profile>> ;\r
+\r
+: user-changed ( -- )\r
+    logged-in-user get t >>changed? drop ;\r
+\r
+: uget ( key -- value )\r
+    profile at ;\r
+\r
+: uset ( value key -- )\r
+    profile set-at\r
+    user-changed ;\r
+\r
+: uchange ( quot key -- )\r
+    profile swap change-at\r
+    user-changed ; inline\r
+\r
+SYMBOL: capabilities\r
+\r
+V{ } clone capabilities set-global\r
+\r
+: define-capability ( word -- ) capabilities get adjoin ;\r
diff --git a/extra/furnace/auth/basic/basic.factor b/extra/furnace/auth/basic/basic.factor
new file mode 100755 (executable)
index 0000000..c57f78b
--- /dev/null
@@ -0,0 +1,41 @@
+! Copyright (c) 2007 Chris Double.\r
+! See http://factorcode.org/license.txt for BSD license.\r
+USING: accessors quotations assocs kernel splitting\r
+base64 html.elements io combinators http.server\r
+furnace.auth.providers furnace.auth.login\r
+http sequences ;\r
+IN: furnace.auth.basic\r
+\r
+TUPLE: basic-auth < filter-responder realm provider ;\r
+\r
+C: <basic-auth> basic-auth\r
+\r
+: authorization-ok? ( provider header -- ? )\r
+    #! Given the realm and the 'Authorization' header,\r
+    #! authenticate the user.\r
+    dup [\r
+        " " split1 swap "Basic" = [\r
+            base64> ":" split1 spin check-login\r
+        ] [\r
+            2drop f\r
+        ] if\r
+    ] [\r
+        2drop f\r
+    ] if ;\r
+\r
+: <401> ( realm -- response )\r
+    401 "Unauthorized" <trivial-response>\r
+    "Basic realm=\"" rot "\"" 3append\r
+    "WWW-Authenticate" set-header\r
+    [\r
+        <html> <body>\r
+            "Username or Password is invalid" write\r
+        </body> </html>\r
+    ] >>body ;\r
+\r
+: logged-in? ( request responder -- ? )\r
+    provider>> swap "authorization" header authorization-ok? ;\r
+\r
+M: basic-auth call-responder* ( request path responder -- response )\r
+    pick over logged-in?\r
+    [ call-next-method ] [ 2nip realm>> <401> ] if ;\r
diff --git a/extra/furnace/auth/login/boilerplate.xml b/extra/furnace/auth/login/boilerplate.xml
new file mode 100644 (file)
index 0000000..edc8c32
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+       <h1><t:write-title /></h1>
+
+       <t:call-next-template />
+
+</t:chloe>
diff --git a/extra/furnace/auth/login/edit-profile.xml b/extra/furnace/auth/login/edit-profile.xml
new file mode 100644 (file)
index 0000000..6beaf5d
--- /dev/null
@@ -0,0 +1,70 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+       <t:title>Edit Profile</t:title>
+
+       <t:form t:action="$login/edit-profile">
+
+       <table>
+       
+       <tr>
+               <th class="field-label">User name:</th>
+               <td><t:label t:name="username" /></td>
+       </tr>
+       
+       <tr>
+               <th class="field-label">Real name:</th>
+               <td><t:field t:name="realname" /></td>
+       </tr>
+       
+       <tr>
+               <td></td>
+               <td>Specifying a real name is optional.</td>
+       </tr>
+       
+       <tr>
+               <th class="field-label">Current password:</th>
+               <td><t:password t:name="password" /></td>
+       </tr>
+       
+       <tr>
+               <td></td>
+               <td>If you don't want to change your current password, leave this field blank.</td>
+       </tr>
+       
+       <tr>
+               <th class="field-label">New password:</th>
+               <td><t:password t:name="new-password" /></td>
+       </tr>
+       
+       <tr>
+               <th class="field-label">Verify:</th>
+               <td><t:password t:name="verify-password" /></td>
+       </tr>
+       
+       <tr>
+               <td></td>
+               <td>If you are changing your password, enter it twice to ensure it is correct.</td>
+       </tr>
+       
+       <tr>
+               <th class="field-label">E-mail:</th>
+               <td><t:field t:name="email" /></td>
+       </tr>
+       
+       <tr>
+               <td></td>
+               <td>Specifying an e-mail address is optional. It enables the "recover password" feature.</td>
+       </tr>
+       
+       </table>
+
+       <p>
+               <input type="submit" value="Update" />
+               <t:validation-messages />
+       </p>
+
+       </t:form>
+       
+</t:chloe>
diff --git a/extra/furnace/auth/login/login-tests.factor b/extra/furnace/auth/login/login-tests.factor
new file mode 100755 (executable)
index 0000000..5095ebd
--- /dev/null
@@ -0,0 +1,6 @@
+IN: furnace.auth.login.tests\r
+USING: tools.test furnace.auth.login ;\r
+\r
+\ <login> must-infer\r
+\ allow-registration must-infer\r
+\ allow-password-recovery must-infer\r
diff --git a/extra/furnace/auth/login/login.factor b/extra/furnace/auth/login/login.factor
new file mode 100755 (executable)
index 0000000..85d71b5
--- /dev/null
@@ -0,0 +1,360 @@
+! Copyright (c) 2008 Slava Pestov\r
+! See http://factorcode.org/license.txt for BSD license.\r
+USING: accessors quotations assocs kernel splitting\r
+combinators sequences namespaces hashtables sets\r
+fry arrays threads qualified random validators\r
+io\r
+io.sockets\r
+io.encodings.utf8\r
+io.encodings.string\r
+io.binary\r
+continuations\r
+destructors\r
+checksums\r
+checksums.sha2\r
+validators\r
+html.components\r
+html.elements\r
+urls\r
+http\r
+http.server\r
+furnace.auth\r
+furnace.auth.providers\r
+furnace.auth.providers.db\r
+furnace.actions\r
+furnace.flows\r
+furnace.sessions\r
+furnace.boilerplate ;\r
+QUALIFIED: smtp\r
+IN: furnace.auth.login\r
+\r
+TUPLE: login < dispatcher users checksum ;\r
+\r
+: users ( -- provider )\r
+    login get users>> ;\r
+\r
+: encode-password ( string salt -- bytes )\r
+    [ utf8 encode ] [ 4 >be ] bi* append\r
+    login get checksum>> checksum-bytes ;\r
+\r
+: >>encoded-password ( user string -- user )\r
+    32 random-bits [ encode-password ] keep\r
+    [ >>password ] [ >>salt ] bi* ; inline\r
+\r
+: valid-login? ( password user -- ? )\r
+    [ salt>> encode-password ] [ password>> ] bi = ;\r
+\r
+: check-login ( password username -- user/f )\r
+    users get-user dup [ [ valid-login? ] keep and ] [ 2drop f ] if ;\r
+\r
+! Destructor\r
+TUPLE: user-saver user ;\r
+\r
+C: <user-saver> user-saver\r
+\r
+M: user-saver dispose\r
+    user>> dup changed?>> [ users update-user ] [ drop ] if ;\r
+\r
+: save-user-after ( user -- )\r
+    <user-saver> &dispose drop ;\r
+\r
+! ! ! Login\r
+: successful-login ( user -- response )\r
+    username>> set-uid "$login" end-flow ;\r
+\r
+: login-failed ( -- * )\r
+    "invalid username or password" validation-error\r
+    validation-failed ;\r
+\r
+: <login-action> ( -- action )\r
+    <page-action>\r
+        "$login/login" >>template\r
+\r
+        [\r
+            {\r
+                { "username" [ v-required ] }\r
+                { "password" [ v-required ] }\r
+            } validate-params\r
+\r
+            "password" value\r
+            "username" value check-login\r
+            [ successful-login ] [ login-failed ] if*\r
+        ] >>submit ;\r
+\r
+! ! ! New user registration\r
+\r
+: user-exists ( -- * )\r
+    "username taken" validation-error\r
+    validation-failed ;\r
+\r
+: password-mismatch ( -- * )\r
+    "passwords do not match" validation-error\r
+    validation-failed ;\r
+\r
+: same-password-twice ( -- )\r
+    "new-password" value "verify-password" value =\r
+    [ password-mismatch ] unless ;\r
+\r
+: <register-action> ( -- action )\r
+    <page-action>\r
+        "$login/register" >>template\r
+\r
+        [\r
+            {\r
+                { "username" [ v-username ] }\r
+                { "realname" [ [ v-one-line ] v-optional ] }\r
+                { "new-password" [ v-password ] }\r
+                { "verify-password" [ v-password ] }\r
+                { "email" [ [ v-email ] v-optional ] }\r
+                { "captcha" [ v-captcha ] }\r
+            } validate-params\r
+\r
+            same-password-twice\r
+        ] >>validate\r
+\r
+        [\r
+            "username" value <user>\r
+                "realname" value >>realname\r
+                "new-password" value >>encoded-password\r
+                "email" value >>email\r
+                H{ } clone >>profile\r
+\r
+            users new-user [ user-exists ] unless*\r
+\r
+            login get init-user-profile\r
+\r
+            successful-login\r
+        ] >>submit ;\r
+\r
+! ! ! Editing user profile\r
+\r
+: <edit-profile-action> ( -- action )\r
+    <page-action>\r
+        [\r
+            logged-in-user get\r
+            [ username>> "username" set-value ]\r
+            [ realname>> "realname" set-value ]\r
+            [ email>> "email" set-value ]\r
+            tri\r
+        ] >>init\r
+\r
+        "$login/edit-profile" >>template\r
+\r
+        [\r
+            uid "username" set-value\r
+\r
+            {\r
+                { "realname" [ [ v-one-line ] v-optional ] }\r
+                { "password" [ ] }\r
+                { "new-password" [ [ v-password ] v-optional ] }\r
+                { "verify-password" [ [ v-password ] v-optional ] } \r
+                { "email" [ [ v-email ] v-optional ] }\r
+            } validate-params\r
+\r
+            { "password" "new-password" "verify-password" }\r
+            [ value empty? not ] contains? [\r
+                "password" value uid check-login\r
+                [ "incorrect password" validation-error ] unless\r
+\r
+                same-password-twice\r
+            ] when\r
+        ] >>validate\r
+\r
+        [\r
+            logged-in-user get\r
+\r
+            "new-password" value dup empty?\r
+            [ drop ] [ >>encoded-password ] if\r
+\r
+            "realname" value >>realname\r
+            "email" value >>email\r
+\r
+            t >>changed?\r
+\r
+            drop\r
+\r
+            "$login" end-flow\r
+        ] >>submit ;\r
+\r
+! ! ! Password recovery\r
+\r
+SYMBOL: lost-password-from\r
+\r
+: current-host ( -- string )\r
+    request get url>> host>> host-name or ;\r
+\r
+: new-password-url ( user -- url )\r
+    "recover-3"\r
+    swap [\r
+        [ username>> "username" set ]\r
+        [ ticket>> "ticket" set ]\r
+        bi\r
+    ] H{ } make-assoc\r
+    derive-url ;\r
+\r
+: password-email ( user -- email )\r
+    smtp:<email>\r
+        [ "[ " % current-host % " ] password recovery" % ] "" make >>subject\r
+        lost-password-from get >>from\r
+        over email>> 1array >>to\r
+        [\r
+            "This e-mail was sent by the application server on " % current-host % "\n" %\r
+            "because somebody, maybe you, clicked on a ``recover password'' link in the\n" %\r
+            "login form, and requested a new password for the user named ``" %\r
+            over username>> % "''.\n" %\r
+            "\n" %\r
+            "If you believe that this request was legitimate, you may click the below link in\n" %\r
+            "your browser to set a new password for your account:\n" %\r
+            "\n" %\r
+            swap new-password-url %\r
+            "\n\n" %\r
+            "Love,\n" %\r
+            "\n" %\r
+            "  FactorBot\n" %\r
+        ] "" make >>body ;\r
+\r
+: send-password-email ( user -- )\r
+    '[ , password-email smtp:send-email ]\r
+    "E-mail send thread" spawn drop ;\r
+\r
+: <recover-action-1> ( -- action )\r
+    <page-action>\r
+        "$login/recover-1" >>template\r
+\r
+        [\r
+            {\r
+                { "username" [ v-username ] }\r
+                { "email" [ v-email ] }\r
+                { "captcha" [ v-captcha ] }\r
+            } validate-params\r
+        ] >>validate\r
+\r
+        [\r
+            "email" value "username" value\r
+            users issue-ticket [\r
+                send-password-email\r
+            ] when*\r
+\r
+            URL" $login/recover-2" <redirect>\r
+        ] >>submit ;\r
+\r
+: <recover-action-2> ( -- action )\r
+    <page-action>\r
+        "$login/recover-2" >>template ;\r
+\r
+: <recover-action-3> ( -- action )\r
+    <page-action>\r
+        [\r
+            {\r
+                { "username" [ v-username ] }\r
+                { "ticket" [ v-required ] }\r
+            } validate-params\r
+        ] >>init\r
+\r
+        "$login/recover-3" >>template\r
+\r
+        [\r
+            {\r
+                { "username" [ v-username ] }\r
+                { "ticket" [ v-required ] }\r
+                { "new-password" [ v-password ] }\r
+                { "verify-password" [ v-password ] }\r
+            } validate-params\r
+\r
+            same-password-twice\r
+        ] >>validate\r
+\r
+        [\r
+            "ticket" value\r
+            "username" value\r
+            users claim-ticket [\r
+                "new-password" value >>encoded-password\r
+                users update-user\r
+\r
+                URL" $login/recover-4" <redirect>\r
+            ] [\r
+                <400>\r
+            ] if*\r
+        ] >>submit ;\r
+\r
+: <recover-action-4> ( -- action )\r
+    <page-action>\r
+        "$login/recover-4" >>template ;\r
+\r
+! ! ! Logout\r
+: <logout-action> ( -- action )\r
+    <action>\r
+        [\r
+            f set-uid\r
+            "$login/login" end-flow\r
+        ] >>submit ;\r
+\r
+! ! ! Authentication logic\r
+\r
+TUPLE: protected < filter-responder capabilities ;\r
+\r
+C: <protected> protected\r
+\r
+: show-login-page ( -- response )\r
+    begin-flow\r
+    URL" $login/login" <redirect> ;\r
+\r
+: check-capabilities ( responder user -- ? )\r
+    [ capabilities>> ] bi@ subset? ;\r
+\r
+M: protected call-responder* ( path responder -- response )\r
+    uid dup [\r
+        users get-user 2dup check-capabilities [\r
+            [ logged-in-user set ] [ save-user-after ] bi\r
+            call-next-method\r
+        ] [\r
+            3drop show-login-page\r
+        ] if\r
+    ] [\r
+        3drop show-login-page\r
+    ] if ;\r
+\r
+M: login call-responder* ( path responder -- response )\r
+    dup login set\r
+    call-next-method ;\r
+\r
+: <login-boilerplate> ( responder -- responder' )\r
+    <boilerplate>\r
+        "$login/boilerplate" >>template ;\r
+\r
+: <login> ( responder -- auth )\r
+    login new-dispatcher\r
+        swap >>default\r
+        <login-action> <login-boilerplate> "login" add-responder\r
+        <logout-action> <login-boilerplate> "logout" add-responder\r
+        users-in-db >>users\r
+        sha-256 >>checksum ;\r
+\r
+! ! ! Configuration\r
+\r
+: allow-edit-profile ( login -- login )\r
+    <edit-profile-action> f <protected> <login-boilerplate>\r
+        "edit-profile" add-responder ;\r
+\r
+: allow-registration ( login -- login )\r
+    <register-action> <login-boilerplate>\r
+        "register" add-responder ;\r
+\r
+: allow-password-recovery ( login -- login )\r
+    <recover-action-1> <login-boilerplate>\r
+        "recover-password" add-responder\r
+    <recover-action-2> <login-boilerplate>\r
+        "recover-2" add-responder\r
+    <recover-action-3> <login-boilerplate>\r
+        "recover-3" add-responder\r
+    <recover-action-4> <login-boilerplate>\r
+        "recover-4" add-responder ;\r
+\r
+: allow-edit-profile? ( -- ? )\r
+    login get responders>> "edit-profile" swap key? ;\r
+\r
+: allow-registration? ( -- ? )\r
+    login get responders>> "register" swap key? ;\r
+\r
+: allow-password-recovery? ( -- ? )\r
+    login get responders>> "recover-password" swap key? ;\r
diff --git a/extra/furnace/auth/login/login.xml b/extra/furnace/auth/login/login.xml
new file mode 100644 (file)
index 0000000..545d7e0
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+       <t:title>Login</t:title>
+
+       <t:form t:action="login">
+
+               <table>
+
+                       <tr>
+                               <th class="field-label">User name:</th>
+                               <td><t:field t:name="username" /></td>
+                       </tr>
+
+                       <tr>
+                               <th class="field-label">Password:</th>
+                               <td><t:password t:name="password" /></td>
+                       </tr>
+
+               </table>
+
+               <p>
+
+                       <input type="submit" value="Log in" />
+                       <t:validation-messages />
+
+               </p>
+
+       </t:form>
+
+       <p>
+               <t:if code="http.server.auth.login:login-failed?">
+                       <t:a t:href="register">Register</t:a>
+               </t:if>
+               |
+               <t:if code="http.server.auth.login:allow-password-recovery?">
+                       <t:a t:href="recover-password">Recover Password</t:a>
+               </t:if>
+       </p>
+
+</t:chloe>
diff --git a/extra/furnace/auth/login/recover-1.xml b/extra/furnace/auth/login/recover-1.xml
new file mode 100644 (file)
index 0000000..21fbe6f
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+       <t:title>Recover lost password: step 1 of 4</t:title>
+
+       <p>Enter the username and e-mail address you used to register for this site, and you will receive a link for activating a new password.</p>
+
+       <t:form t:action="recover-password">
+
+               <table>
+
+                       <tr>
+                               <th class="field-label">User name:</th>
+                               <td><t:field t:name="username" /></td>
+                       </tr>
+
+                       <tr>
+                               <th class="field-label">E-mail:</th>
+                               <td><t:field t:name="email" /></td>
+                       </tr>
+
+                       <tr>
+                               <th class="field-label">Captcha:</th>
+                               <td><t:field t:name="captcha" /></td>
+                       </tr>
+
+                       <tr>
+                               <td></td>
+                               <td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to e-mail you will be blocked.</td>
+                       </tr>
+
+               </table>
+
+               <input type="submit" value="Recover password" />
+
+       </t:form>
+
+</t:chloe>
diff --git a/extra/furnace/auth/login/recover-2.xml b/extra/furnace/auth/login/recover-2.xml
new file mode 100644 (file)
index 0000000..c7819bd
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+       <t:title>Recover lost password: step 2 of 4</t:title>
+
+       <p>If you entered the correct username and e-mail address, you should receive an e-mail shortly. Click the link in the e-mail for further instructions.</p>
+
+</t:chloe>
diff --git a/extra/furnace/auth/login/recover-3.xml b/extra/furnace/auth/login/recover-3.xml
new file mode 100644 (file)
index 0000000..2e412d1
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+       <t:title>Recover lost password: step 3 of 4</t:title>
+
+       <p>Choose a new password for your account.</p>
+
+       <t:form t:action="new-password">
+
+               <table>
+
+                       <t:hidden t:name="username" />
+                       <t:hidden t:name="ticket" />
+
+                       <tr>
+                               <th class="field-label">Password:</th>
+                               <td><t:password t:name="new-password" /></td>
+                       </tr>
+
+                       <tr>
+                               <th class="field-label">Verify password:</th>
+                               <td><t:password t:name="verify-password" /></td>
+                       </tr>
+
+                       <tr>
+                               <td></td>
+                               <td>Enter your password twice to ensure it is correct.</td>
+                       </tr>
+
+               </table>
+
+               <p>
+                       <input type="submit" value="Set password" />
+                       <t:validation-messages />
+               </p>
+
+       </t:form>
+
+</t:chloe>
diff --git a/extra/furnace/auth/login/recover-4.xml b/extra/furnace/auth/login/recover-4.xml
new file mode 100755 (executable)
index 0000000..f5d02fa
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version='1.0' ?>\r
+\r
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">\r
+\r
+       <t:title>Recover lost password: step 4 of 4</t:title>\r
+\r
+       <p>Your password has been reset. You may now <t:a t:href="login">log in</t:a>.</p>\r
+\r
+</t:chloe>\r
diff --git a/extra/furnace/auth/login/register.xml b/extra/furnace/auth/login/register.xml
new file mode 100644 (file)
index 0000000..9815f21
--- /dev/null
@@ -0,0 +1,72 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+       <t:title>New User Registration</t:title>
+
+       <t:form t:action="register">
+
+               <table>
+
+                       <tr>
+                               <th class="field-label">User name:</th>
+                               <td><t:field t:name="username" /></td>
+                       </tr>
+
+                       <tr>
+                               <th class="field-label">Real name:</th>
+                               <td><t:field t:name="realname" /></td>
+                       </tr>
+
+                       <tr>
+                               <td></td>
+                               <td>Specifying a real name is optional.</td>
+                       </tr>
+
+                       <tr>
+                               <th class="field-label">Password:</th>
+                               <td><t:password t:name="new-password" /></td>
+                       </tr>
+
+                       <tr>
+                               <th class="field-label">Verify:</th>
+                               <td><t:password t:name="verify-password" /></td>
+                       </tr>
+
+                       <tr>
+                               <td></td>
+                               <td>Enter your password twice to ensure it is correct.</td>
+                       </tr>
+
+                       <tr>
+                               <th class="field-label">E-mail:</th>
+                               <td><t:field t:name="email" /></td>
+                       </tr>
+
+                       <tr>
+                               <td></td>
+                               <td>Specifying an e-mail address is optional. It enables the "recover password" feature.</td>
+                       </tr>
+
+                       <tr>
+                               <th class="field-label">Captcha:</th>
+                               <td><t:field t:name="captcha" /></td>
+                       </tr>
+
+                       <tr>
+                               <td></td>
+                               <td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to register will be blocked.</td>
+                       </tr>
+
+               </table>
+
+               <p>
+
+                       <input type="submit" value="Register" />
+                       <t:validation-messages />
+
+               </p>
+
+       </t:form>
+
+</t:chloe>
diff --git a/extra/furnace/auth/providers/assoc/assoc-tests.factor b/extra/furnace/auth/providers/assoc/assoc-tests.factor
new file mode 100755 (executable)
index 0000000..8f9eeaa
--- /dev/null
@@ -0,0 +1,35 @@
+IN: furnace.auth.providers.assoc.tests\r
+USING: furnace.actions furnace.auth.providers \r
+furnace.auth.providers.assoc furnace.auth.login\r
+tools.test namespaces accessors kernel ;\r
+\r
+<action> <login>\r
+    <users-in-memory> >>users\r
+login set\r
+\r
+[ t ] [\r
+    "slava" <user>\r
+        "foobar" >>encoded-password\r
+        "slava@factorcode.org" >>email\r
+        H{ } clone >>profile\r
+    users new-user\r
+    username>> "slava" =\r
+] unit-test\r
+\r
+[ f ] [\r
+    "slava" <user>\r
+        H{ } clone >>profile\r
+    users new-user\r
+] unit-test\r
+\r
+[ f ] [ "fdasf" "slava" check-login >boolean ] unit-test\r
+\r
+[ ] [ "foobar" "slava" check-login "user" set ] unit-test\r
+\r
+[ t ] [ "user" get >boolean ] unit-test\r
+\r
+[ ] [ "user" get "fdasf" >>encoded-password drop ] unit-test\r
+\r
+[ t ] [ "fdasf" "slava" check-login >boolean ] unit-test\r
+\r
+[ f ] [ "foobar" "slava" check-login >boolean ] unit-test\r
diff --git a/extra/furnace/auth/providers/assoc/assoc.factor b/extra/furnace/auth/providers/assoc/assoc.factor
new file mode 100755 (executable)
index 0000000..f5a79d7
--- /dev/null
@@ -0,0 +1,18 @@
+! Copyright (C) 2008 Slava Pestov.\r
+! See http://factorcode.org/license.txt for BSD license.\r
+IN: furnace.auth.providers.assoc\r
+USING: accessors assocs kernel furnace.auth.providers ;\r
+\r
+TUPLE: users-in-memory assoc ;\r
+\r
+: <users-in-memory> ( -- provider )\r
+    H{ } clone users-in-memory boa ;\r
+\r
+M: users-in-memory get-user ( username provider -- user/f )\r
+    assoc>> at ;\r
+\r
+M: users-in-memory update-user ( user provider -- ) 2drop ;\r
+\r
+M: users-in-memory new-user ( user provider -- user/f )\r
+    [ dup username>> ] dip assoc>>\r
+    2dup key? [ 3drop f ] [ pick [ set-at ] dip ] if ;\r
diff --git a/extra/furnace/auth/providers/db/db-tests.factor b/extra/furnace/auth/providers/db/db-tests.factor
new file mode 100755 (executable)
index 0000000..714dcb4
--- /dev/null
@@ -0,0 +1,47 @@
+IN: furnace.auth.providers.db.tests\r
+USING: furnace.actions\r
+furnace.auth.login\r
+furnace.auth.providers\r
+furnace.auth.providers.db tools.test\r
+namespaces db db.sqlite db.tuples continuations\r
+io.files accessors kernel ;\r
+\r
+<action> <login>\r
+    users-in-db >>users\r
+login set\r
+\r
+[ "auth-test.db" temp-file delete-file ] ignore-errors\r
+\r
+"auth-test.db" temp-file sqlite-db [\r
+\r
+    init-users-table\r
+\r
+    [ t ] [\r
+        "slava" <user>\r
+            "foobar" >>encoded-password\r
+            "slava@factorcode.org" >>email\r
+            H{ } clone >>profile\r
+            users new-user\r
+            username>> "slava" =\r
+    ] unit-test\r
+\r
+    [ f ] [\r
+        "slava" <user>\r
+            H{ } clone >>profile\r
+        users new-user\r
+    ] unit-test\r
+\r
+    [ f ] [ "fdasf" "slava" check-login >boolean ] unit-test\r
+\r
+    [ ] [ "foobar" "slava" check-login "user" set ] unit-test\r
+\r
+    [ t ] [ "user" get >boolean ] unit-test\r
+\r
+    [ ] [ "user" get "fdasf" >>encoded-password drop ] unit-test\r
+\r
+    [ ] [ "user" get users update-user ] unit-test\r
+\r
+    [ t ] [ "fdasf" "slava" check-login >boolean ] unit-test\r
+\r
+    [ f ] [ "foobar" "slava" check-login >boolean ] unit-test\r
+] with-db\r
diff --git a/extra/furnace/auth/providers/db/db.factor b/extra/furnace/auth/providers/db/db.factor
new file mode 100755 (executable)
index 0000000..90306e5
--- /dev/null
@@ -0,0 +1,41 @@
+! Copyright (C) 2008 Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+USING: db db.tuples db.types accessors
+furnace.auth.providers kernel continuations
+classes.singleton ;
+IN: furnace.auth.providers.db
+
+user "USERS"
+{
+    { "username" "USERNAME" { VARCHAR 256 } +user-assigned-id+ }
+    { "realname" "REALNAME" { VARCHAR 256 } }
+    { "password" "PASSWORD" BLOB +not-null+ }
+    { "salt" "SALT" INTEGER +not-null+ }
+    { "email" "EMAIL" { VARCHAR 256 } }
+    { "ticket" "TICKET" { VARCHAR 256 } }
+    { "capabilities" "CAPABILITIES" FACTOR-BLOB }
+    { "profile" "PROFILE" FACTOR-BLOB }
+    { "deleted" "DELETED" INTEGER +not-null+ }
+} define-persistent
+
+: init-users-table user ensure-table ;
+
+SINGLETON: users-in-db
+
+M: users-in-db get-user
+    drop <user> select-tuple ;
+
+M: users-in-db new-user
+    drop
+    [
+        user new
+            over username>> >>username
+        select-tuple [
+            drop f
+        ] [
+            dup insert-tuple
+        ] if
+    ] with-transaction ;
+
+M: users-in-db update-user
+    drop update-tuple ;
diff --git a/extra/furnace/auth/providers/null/null.factor b/extra/furnace/auth/providers/null/null.factor
new file mode 100755 (executable)
index 0000000..39ea812
--- /dev/null
@@ -0,0 +1,14 @@
+! Copyright (C) 2008 Slava Pestov.\r
+! See http://factorcode.org/license.txt for BSD license.\r
+USING: furnace.auth.providers kernel ;\r
+IN: furnace.auth.providers.null\r
+\r
+TUPLE: no-users ;\r
+\r
+: no-users T{ no-users } ;\r
+\r
+M: no-users get-user 2drop f ;\r
+\r
+M: no-users new-user 2drop f ;\r
+\r
+M: no-users update-user 2drop ;\r
diff --git a/extra/furnace/auth/providers/providers.factor b/extra/furnace/auth/providers/providers.factor
new file mode 100755 (executable)
index 0000000..1933fc8
--- /dev/null
@@ -0,0 +1,50 @@
+! Copyright (C) 2008 Slava Pestov.\r
+! See http://factorcode.org/license.txt for BSD license.\r
+USING: kernel accessors random math.parser locals\r
+sequences math ;\r
+IN: furnace.auth.providers\r
+\r
+TUPLE: user\r
+username realname\r
+password salt\r
+email ticket capabilities profile deleted changed? ;\r
+\r
+: <user> ( username -- user )\r
+    user new\r
+        swap >>username\r
+        0 >>deleted ;\r
+\r
+GENERIC: get-user ( username provider -- user/f )\r
+\r
+GENERIC: update-user ( user provider -- )\r
+\r
+GENERIC: new-user ( user provider -- user/f )\r
+\r
+! Password recovery support\r
+\r
+:: issue-ticket ( email username provider -- user/f )\r
+    [let | user [ username provider get-user ] |\r
+        user [\r
+            user email>> length 0 > [\r
+                user email>> email = [\r
+                    user\r
+                    256 random-bits >hex >>ticket\r
+                    dup provider update-user\r
+                ] [ f ] if\r
+            ] [ f ] if\r
+        ] [ f ] if\r
+    ] ;\r
+\r
+:: claim-ticket ( ticket username provider -- user/f )\r
+    [let | user [ username provider get-user ] |\r
+        user [\r
+            user ticket>> ticket = [\r
+                user f >>ticket dup provider update-user\r
+            ] [ f ] if\r
+        ] [ f ] if\r
+    ] ;\r
+\r
+! For configuration\r
+\r
+: add-user ( provider user -- provider )\r
+    over new-user [ "User exists" throw ] when ;\r
diff --git a/extra/furnace/boilerplate/boilerplate.factor b/extra/furnace/boilerplate/boilerplate.factor
new file mode 100644 (file)
index 0000000..ec84ba1
--- /dev/null
@@ -0,0 +1,21 @@
+! Copyright (c) 2008 Slava Pestov
+! See http://factorcode.org/license.txt for BSD license.
+USING: accessors kernel namespaces http.server html.templates
+html.templates.chloe locals ;
+IN: furnace.boilerplate
+
+TUPLE: boilerplate < filter-responder template ;
+
+: <boilerplate> f boilerplate boa ;
+
+M:: boilerplate call-responder* ( path responder -- )
+    path responder call-next-method
+    dup content-type>> "text/html" = [
+        clone [| body |
+            [
+                body
+                responder template>> resolve-template-path <chloe>
+                with-boilerplate
+            ]
+        ] change-body
+    ] when ;
diff --git a/extra/furnace/callbacks/callbacks-tests.factor b/extra/furnace/callbacks/callbacks-tests.factor
new file mode 100755 (executable)
index 0000000..f72aad3
--- /dev/null
@@ -0,0 +1,68 @@
+IN: furnace.callbacks\r
+USING: furnace.actions furnace.callbacks accessors\r
+http.server http tools.test namespaces io fry sequences\r
+splitting kernel hashtables continuations ;\r
+\r
+[ 123 ] [\r
+    [\r
+        init-request\r
+\r
+        <request> "GET" >>method request set\r
+        [\r
+            exit-continuation set\r
+            { }\r
+            <action> [ [ "hello" print 123 ] show-final ] >>display\r
+            <callback-responder>\r
+            call-responder\r
+        ] callcc1\r
+    ] with-scope\r
+] unit-test\r
+\r
+[\r
+    init-request\r
+\r
+    <action> [\r
+        [\r
+            "hello" print\r
+            "text/html" <content>\r
+        ] show-page\r
+        "byebye" print\r
+        [ 123 ] show-final\r
+    ] >>display\r
+    <callback-responder> "r" set\r
+\r
+    [ 123 ] [\r
+        [\r
+            exit-continuation set\r
+            <request> "GET" >>method request set\r
+            { } "r" get call-responder\r
+        ] callcc1\r
+\r
+        body>> first\r
+\r
+        <request>\r
+            "GET" >>method\r
+            swap cont-id associate >>query\r
+            "/" >>path\r
+        request set\r
+\r
+        [\r
+            exit-continuation set\r
+            { }\r
+            "r" get call-responder\r
+        ] callcc1\r
+\r
+        ! get-post-get\r
+        <request>\r
+            "GET" >>method\r
+            swap "location" header "=" last-split1 nip cont-id associate >>query\r
+            "/" >>path\r
+        request set\r
+\r
+        [\r
+            exit-continuation set\r
+            { }\r
+            "r" get call-responder\r
+        ] callcc1\r
+    ] unit-test\r
+] with-scope\r
diff --git a/extra/furnace/callbacks/callbacks.factor b/extra/furnace/callbacks/callbacks.factor
new file mode 100755 (executable)
index 0000000..7b18afe
--- /dev/null
@@ -0,0 +1,123 @@
+! Copyright (C) 2004 Chris Double.\r
+! Copyright (C) 2006, 2008 Slava Pestov.\r
+! See http://factorcode.org/license.txt for BSD license.\r
+USING: http http.server io kernel math namespaces\r
+continuations calendar sequences assocs hashtables\r
+accessors arrays alarms quotations combinators fry assocs.lib ;\r
+IN: furnace.callbacks\r
+\r
+SYMBOL: responder\r
+\r
+TUPLE: callback-responder responder callbacks ;\r
+\r
+: <callback-responder> ( responder -- responder' )\r
+    #! A continuation responder is a special type of session\r
+    #! manager. However it works entirely differently from\r
+    #! the URL and cookie session managers.\r
+    H{ } clone callback-responder boa ;\r
+\r
+TUPLE: callback cont quot expires alarm responder ;\r
+\r
+: timeout 20 minutes ;\r
+\r
+: timeout-callback ( callback -- )\r
+    [ alarm>> cancel-alarm ]\r
+    [ dup responder>> callbacks>> delete-at ]\r
+    bi ;\r
+\r
+: touch-callback ( callback -- )\r
+    dup expires>> [\r
+        dup alarm>> [ cancel-alarm ] when*\r
+        dup '[ , timeout-callback ] timeout later >>alarm\r
+    ] when drop ;\r
+\r
+: <callback> ( cont quot expires? -- callback )\r
+    f callback-responder get callback boa\r
+    dup touch-callback ;\r
+\r
+: invoke-callback ( callback -- response )\r
+    [ touch-callback ]\r
+    [ quot>> request get exit-continuation get 3array ]\r
+    [ cont>> continue-with ]\r
+    tri ;\r
+\r
+: register-callback ( cont quot expires? -- id )\r
+    <callback> callback-responder get callbacks>> set-at-unique ;\r
+\r
+: forward-to-url ( url query -- * )\r
+    #! When executed inside a 'show' call, this will force a\r
+    #! HTTP 302 to occur to instruct the browser to forward to\r
+    #! the request URL.\r
+    <temporary-redirect> exit-with ;\r
+\r
+: cont-id "factorcontid" ;\r
+\r
+: forward-to-id ( id -- * )\r
+    #! When executed inside a 'show' call, this will force a\r
+    #! HTTP 302 to occur to instruct the browser to forward to\r
+    #! the request URL.\r
+    f swap cont-id associate forward-to-url ;\r
+\r
+: restore-request ( pair -- )\r
+    first3 exit-continuation set request set call ;\r
+\r
+SYMBOL: post-refresh-get?\r
+\r
+: redirect-to-here ( -- )\r
+    #! Force a redirect to the client browser so that the browser\r
+    #! goes to the current point in the code. This forces an URL\r
+    #! change on the browser so that refreshing that URL will\r
+    #! immediately run from this code point. This prevents the\r
+    #! "this request will issue a POST" warning from the browser\r
+    #! and prevents re-running the previous POST logic. This is\r
+    #! known as the 'post-refresh-get' pattern.\r
+    post-refresh-get? get [\r
+        [\r
+            [ ] t register-callback forward-to-id\r
+        ] callcc1 restore-request\r
+    ] [\r
+        post-refresh-get? on\r
+    ] if ;\r
+\r
+SYMBOL: current-show\r
+\r
+: store-current-show ( -- )\r
+    #! Store the current continuation in the variable 'current-show'\r
+    #! so it can be returned to later by 'quot-id'. Note that it\r
+    #! recalls itself when the continuation is called to ensure that\r
+    #! it resets its value back to the most recent show call.\r
+    [ current-show set f ] callcc1\r
+    [ restore-request store-current-show ] when* ;\r
+\r
+: show-final ( quot -- * )\r
+    [ redirect-to-here store-current-show ] dip\r
+    call exit-with ; inline\r
+\r
+: resuming-callback ( responder request -- id )\r
+    cont-id query-param swap callbacks>> at ;\r
+\r
+M: callback-responder call-responder* ( path responder -- response )\r
+    '[\r
+        , ,\r
+\r
+        [ callback-responder set ]\r
+        [ request get resuming-callback ] bi\r
+\r
+        [\r
+            invoke-callback\r
+        ] [\r
+            callback-responder get responder>> call-responder\r
+        ] ?if\r
+    ] with-exit-continuation ;\r
+\r
+: show-page ( quot -- )\r
+    [ redirect-to-here store-current-show ] dip\r
+    [\r
+        [ ] t register-callback swap call exit-with\r
+    ] callcc1 restore-request ; inline\r
+\r
+: quot-id ( quot -- id )\r
+    current-show get swap t register-callback ;\r
+\r
+: quot-url ( quot -- url )\r
+    quot-id f swap cont-id associate derive-url ;\r
diff --git a/extra/furnace/db/db-tests.factor b/extra/furnace/db/db-tests.factor
new file mode 100644 (file)
index 0000000..34357ae
--- /dev/null
@@ -0,0 +1,4 @@
+IN: furnace.db.tests
+USING: tools.test furnace.db ;
+
+\ <db-persistence> must-infer
diff --git a/extra/furnace/db/db.factor b/extra/furnace/db/db.factor
new file mode 100755 (executable)
index 0000000..8d70270
--- /dev/null
@@ -0,0 +1,17 @@
+! Copyright (C) 2008 Slava Pestov.\r
+! See http://factorcode.org/license.txt for BSD license.\r
+USING: db db.pools io.pools http.server furnace.sessions\r
+kernel accessors continuations namespaces destructors ;\r
+IN: furnace.db\r
+\r
+TUPLE: db-persistence < filter-responder pool ;\r
+\r
+: <db-persistence> ( responder params db -- responder' )\r
+    <db-pool> db-persistence boa ;\r
+\r
+M: db-persistence call-responder*\r
+    [\r
+        pool>> [ acquire-connection ] keep\r
+        [ return-connection-later ] [ drop db set ] 2bi\r
+    ]\r
+    [ call-next-method ] bi ;\r
diff --git a/extra/furnace/flows/flows.factor b/extra/furnace/flows/flows.factor
new file mode 100644 (file)
index 0000000..0013350
--- /dev/null
@@ -0,0 +1,78 @@
+! Copyright (C) 2008 Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+USING: accessors namespaces sequences arrays kernel
+assocs assocs.lib hashtables math.parser urls combinators
+html.elements http http.server furnace.sessions
+html.templates.chloe.syntax ;
+IN: furnace.flows
+
+TUPLE: flows < filter-responder ;
+
+C: <flows> flows
+
+: begin-flow* ( -- id )
+    request get
+    [ url>> ] [ post-data>> ] [ method>> ] tri 3array
+    flows sget set-at-unique
+    session-changed ;
+
+: end-flow-post ( url post-data -- response )
+    request [
+        clone
+            "POST" >>method
+            swap >>post-data
+            swap >>url
+    ] change
+    request get url>> path>> split-path
+    flows get responder>> call-responder ;
+
+: end-flow* ( url id -- response )
+    flows sget at [
+        first3 {
+            { "GET" [ drop <redirect> ] }
+            { "HEAD" [ drop <redirect> ] }
+            { "POST" [ end-flow-post ] }
+        } case
+    ] [ <redirect> ] ?if ;
+
+SYMBOL: flow-id
+
+: flow-id-key "factorflowid" ;
+
+: begin-flow ( -- )
+    begin-flow* flow-id set ;
+
+: end-flow ( default -- response )
+    flow-id get end-flow* ;
+
+M: flows call-responder*
+    dup flows set
+    flow-id-key request get request-params at flow-id set
+    call-next-method ;
+
+M: flows init-session*
+    H{ } clone flows sset
+    call-next-method ;
+
+M: flows link-attr ( tag -- )
+    drop
+    "flow" optional-attr {
+        { "none" [ flow-id off ] }
+        { "begin" [ begin-flow ] }
+        { "current" [ ] }
+        { f [ ] }
+    } case ;
+
+M: flows modify-query ( query responder -- query' )
+    drop
+    flow-id get [ flow-id-key associate assoc-union ] when* ;
+
+M: flows hidden-form-field ( responder -- )
+    drop
+    flow-id get [
+        <input
+            "hidden" =type
+            flow-id-key =name
+            =value
+        input/>
+    ] when* ;
diff --git a/extra/furnace/furnace.factor b/extra/furnace/furnace.factor
new file mode 100644 (file)
index 0000000..80c9f94
--- /dev/null
@@ -0,0 +1,136 @@
+! Copyright (C) 2003, 2008 Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+IN: furnace
+
+GENERIC: hidden-form-field ( responder -- )
+
+M: object hidden-form-field drop ;
+
+: request-params ( request -- assoc )
+    dup method>> {
+        { "GET" [ url>> query>> ] }
+        { "HEAD" [ url>> query>> ] }
+        { "POST" [ post-data>> ] }
+    } case ;
+
+: <feed-content> ( body -- response )
+    feed>xml "application/atom+xml" <content> ;
+
+: <json-content> ( obj -- response )
+    >json "application/json" <content> ;
+
+SYMBOL: exit-continuation
+
+: exit-with exit-continuation get continue-with ;
+
+: with-exit-continuation ( quot -- )
+    '[ exit-continuation set @ ] callcc1 exit-continuation off ;
+
+! Chloe tags
+: parse-query-attr ( string -- assoc )
+    dup empty?
+    [ drop f ] [ "," split [ dup value ] H{ } map>assoc ] if ;
+
+CHLOE: atom
+    [ "title" required-attr ]
+    [ "href" required-attr ]
+    [ "query" optional-attr parse-query-attr ] tri
+    <url>
+        swap >>query
+        swap >>path
+    adjust-url
+    add-atom-feed ;
+
+CHLOE: write-atom drop write-atom-feeds ;
+
+GENERIC: link-attr ( tag responder -- )
+
+M: object link-attr 2drop ;
+
+: link-attrs ( tag -- )
+    '[ , _ link-attr ] each-responder ;
+
+: a-start-tag ( tag -- )
+    [
+        <a
+            dup link-attrs
+            dup "value" optional-attr [ value f ] [
+                [ "href" required-attr ]
+                [ "query" optional-attr parse-query-attr ]
+                bi
+            ] ?if
+            <url>
+                swap >>query
+                swap >>path
+            adjust-url =href
+        a>
+    ] with-scope ;
+
+CHLOE: a
+    [ a-start-tag ]
+    [ process-tag-children ]
+    [ drop </a> ]
+    tri ;
+
+: form-start-tag ( tag -- )
+    [
+        [
+            <form
+            "POST" =method
+            [ link-attrs ]
+            [ "action" required-attr resolve-base-path =action ]
+            [ tag-attrs non-chloe-attrs-only print-attrs ]
+            tri
+            form>
+        ] [
+            [ hidden-form-field ] each-responder
+            "for" optional-attr [ hidden render ] when*
+        ] bi
+    ] with-scope ;
+
+CHLOE: form
+    [ form-start-tag ]
+    [ process-tag-children ]
+    [ drop </form> ]
+    tri ;
+
+DEFER: process-chloe-tag
+
+STRING: button-tag-markup
+<t:form class="inline" xmlns:t="http://factorcode.org/chloe/1.0">
+    <button type="submit"></button>
+</t:form>
+;
+
+: add-tag-attrs ( attrs tag -- )
+    tag-attrs swap update ;
+
+CHLOE: button
+    button-tag-markup string>xml delegate
+    {
+        [ [ tag-attrs chloe-attrs-only ] dip add-tag-attrs ]
+        [ [ tag-attrs non-chloe-attrs-only ] dip "button" tag-named add-tag-attrs ]
+        [ [ children>string 1array ] dip "button" tag-named set-tag-children ]
+        [ nip ]
+    } 2cleave process-chloe-tag ;
+
+: attr>word ( value -- word/f )
+    dup ":" split1 swap lookup
+    [ ] [ "No such word: " swap append throw ] ?if ;
+
+: attr>var ( value -- word/f )
+    attr>word dup symbol? [
+        "Must be a symbol: " swap append throw
+    ] unless ;
+
+: if-satisfied? ( tag -- ? )
+    t swap
+    {
+        [ "code"  optional-attr [ attr>word execute and ] when* ]
+        [  "var"  optional-attr [ attr>var      get and ] when* ]
+        [ "svar"  optional-attr [ attr>var     sget and ] when* ]
+        [ "uvar"  optional-attr [ attr>var     uget and ] when* ]
+        [ "value" optional-attr [ value             and ] when* ]
+    } cleave ;
+
+CHLOE: if dup if-satisfied? [ process-tag-children ] [ drop ] if ;
diff --git a/extra/furnace/sessions/authors.txt b/extra/furnace/sessions/authors.txt
new file mode 100755 (executable)
index 0000000..7c1b2f2
--- /dev/null
@@ -0,0 +1 @@
+Doug Coleman
diff --git a/extra/furnace/sessions/sessions-tests.factor b/extra/furnace/sessions/sessions-tests.factor
new file mode 100755 (executable)
index 0000000..949d04d
--- /dev/null
@@ -0,0 +1,150 @@
+IN: furnace.sessions.tests\r
+USING: tools.test http furnace.sessions\r
+furnace.actions http.server math namespaces kernel accessors\r
+prettyprint io.streams.string io.files splitting destructors\r
+sequences db db.sqlite continuations urls ;\r
+\r
+: with-session\r
+    [\r
+        [ [ save-session-after ] [ session set ] bi ] dip call\r
+    ] with-destructors ; inline\r
+\r
+TUPLE: foo ;\r
+\r
+C: <foo> foo\r
+\r
+M: foo init-session* drop 0 "x" sset ;\r
+\r
+M: foo call-responder*\r
+    2drop\r
+    "x" [ 1+ ] schange\r
+    "x" sget number>string "text/html" <content> ;\r
+\r
+: url-responder-mock-test\r
+    [\r
+        <request>\r
+            "GET" >>method\r
+            dup url>>\r
+                "id" get session-id-key set-query-param\r
+                "/" >>path drop\r
+        init-request\r
+        { } sessions get call-responder\r
+        [ write-response-body drop ] with-string-writer\r
+    ] with-destructors ;\r
+\r
+: sessions-mock-test\r
+    [\r
+        <request>\r
+            "GET" >>method\r
+            "cookies" get >>cookies\r
+            dup url>> "/" >>path drop\r
+        init-request\r
+        { } sessions get call-responder\r
+        [ write-response-body drop ] with-string-writer\r
+    ] with-destructors ;\r
+\r
+: <exiting-action>\r
+    <action>\r
+        [ [ ] "text/plain" <content> exit-with ] >>display ;\r
+\r
+[ "auth-test.db" temp-file sqlite-db delete-file ] ignore-errors\r
+\r
+"auth-test.db" temp-file sqlite-db [\r
+\r
+    <request> init-request\r
+    init-sessions-table\r
+\r
+    [ ] [\r
+        <foo> <sessions>\r
+        sessions set\r
+    ] unit-test\r
+\r
+    [\r
+        [ ] [\r
+            empty-session\r
+                123 >>id session set\r
+        ] unit-test\r
+\r
+        [ ] [ 3 "x" sset ] unit-test\r
+\r
+        [ 9 ] [ "x" sget sq ] unit-test\r
+\r
+        [ ] [ "x" [ 1- ] schange ] unit-test\r
+\r
+        [ 4 ] [ "x" sget sq ] unit-test\r
+\r
+        [ t ] [ session get changed?>> ] unit-test\r
+    ] with-scope\r
+\r
+    [ t ] [\r
+        begin-session id>>\r
+        get-session session?\r
+    ] unit-test\r
+\r
+    [ { 5 0 } ] [\r
+        [\r
+            begin-session\r
+            dup [ 5 "a" sset ] with-session\r
+            dup [ "a" sget , ] with-session\r
+            dup [ "x" sget , ] with-session\r
+            drop\r
+        ] { } make\r
+    ] unit-test\r
+\r
+    [ 0 ] [\r
+        begin-session id>>\r
+        get-session [ "x" sget ] with-session\r
+    ] unit-test\r
+\r
+    [ { 5 0 } ] [\r
+        [\r
+            begin-session id>>\r
+            dup get-session [ 5 "a" sset ] with-session\r
+            dup get-session [ "a" sget , ] with-session\r
+            dup get-session [ "x" sget , ] with-session\r
+            drop\r
+        ] { } make\r
+    ] unit-test\r
+\r
+    [ ] [\r
+        <foo> <sessions>\r
+        sessions set\r
+    ] unit-test\r
+\r
+    [\r
+        <request>\r
+            "GET" >>method\r
+            dup url>> "/" >>path drop\r
+        request set\r
+        { "etc" } sessions get call-responder response set\r
+        [ "1" ] [ [ response get write-response-body drop ] with-string-writer ] unit-test\r
+        response get\r
+    ] with-destructors\r
+    response set\r
+\r
+    [ ] [ response get cookies>> "cookies" set ] unit-test\r
+\r
+    [ "2" ] [ sessions-mock-test ] unit-test\r
+    [ "3" ] [ sessions-mock-test ] unit-test\r
+    [ "4" ] [ sessions-mock-test ] unit-test\r
+\r
+    [\r
+        [ ] [\r
+            <request>\r
+                "GET" >>method\r
+                dup url>>\r
+                    "id" get session-id-key set-query-param\r
+                    "/" >>path drop\r
+            request set\r
+\r
+            [\r
+                { } <exiting-action> <sessions>\r
+                call-responder\r
+            ] with-destructors response set\r
+        ] unit-test\r
+\r
+        [ "text/plain" ] [ response get content-type>> ] unit-test\r
+\r
+        [ f ] [ response get cookies>> empty? ] unit-test\r
+    ] with-scope\r
+] with-db\r
diff --git a/extra/furnace/sessions/sessions.factor b/extra/furnace/sessions/sessions.factor
new file mode 100755 (executable)
index 0000000..2b6bf84
--- /dev/null
@@ -0,0 +1,161 @@
+! Copyright (C) 2008 Doug Coleman, Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+USING: assocs kernel math.intervals math.parser namespaces
+random accessors quotations hashtables sequences continuations
+fry calendar combinators destructors alarms
+db db.tuples db.types
+http http.server html.elements html.templates.chloe ;
+IN: furnace.sessions
+
+TUPLE: session id expires uid namespace changed? ;
+
+: <session> ( id -- session )
+    session new
+        swap >>id ;
+
+session "SESSIONS"
+{
+    { "id" "ID" +random-id+ system-random-generator }
+    { "expires" "EXPIRES" TIMESTAMP +not-null+ }
+    { "uid" "UID" { VARCHAR 255 } }
+    { "namespace" "NAMESPACE" FACTOR-BLOB }
+} define-persistent
+
+: get-session ( id -- session )
+    dup [ <session> select-tuple ] when ;
+
+: init-sessions-table session ensure-table ;
+
+: start-expiring-sessions ( db seq -- )
+    '[
+        , , [
+            session new
+                -1.0/0.0 now [a,b] >>expires
+            delete-tuples
+        ] with-db
+    ] 5 minutes every drop ;
+
+GENERIC: init-session* ( responder -- )
+
+M: object init-session* drop ;
+
+M: dispatcher init-session* default>> init-session* ;
+
+M: filter-responder init-session* responder>> init-session* ;
+
+TUPLE: sessions < filter-responder timeout domain ;
+
+: <sessions> ( responder -- responder' )
+    sessions new
+        swap >>responder
+        20 minutes >>timeout ;
+
+: (session-changed) ( session -- )
+    t >>changed? drop ;
+
+: session-changed ( -- )
+    session get (session-changed) ;
+
+: sget ( key -- value )
+    session get namespace>> at ;
+
+: sset ( value key -- )
+    session get
+    [ namespace>> set-at ] [ (session-changed) ] bi ;
+
+: schange ( key quot -- )
+    session get
+    [ namespace>> swap change-at ] keep
+    (session-changed) ; inline
+
+: uid ( -- uid )
+    session get uid>> ;
+
+: set-uid ( uid -- )
+    session get [ (>>uid) ] [ (session-changed) ] bi ;
+
+: init-session ( session -- )
+    session [ sessions get init-session* ] with-variable ;
+
+: cutoff-time ( -- time )
+    sessions get timeout>> from-now ;
+
+: touch-session ( session -- )
+    cutoff-time >>expires drop ;
+
+: empty-session ( -- session )
+    f <session>
+        H{ } clone >>namespace
+        dup touch-session ;
+
+: begin-session ( -- session )
+    empty-session [ init-session ] [ insert-tuple ] [ ] tri ;
+
+! Destructor
+TUPLE: session-saver session ;
+
+C: <session-saver> session-saver
+
+M: session-saver dispose
+    session>> dup changed?>> [
+        [ touch-session ] [ update-tuple ] bi
+    ] [ drop ] if ;
+
+: save-session-after ( session -- )
+    <session-saver> &dispose drop ;
+
+: existing-session ( path session -- response )
+    [ session set ] [ save-session-after ] bi
+    sessions get responder>> call-responder ;
+
+: session-id-key "factorsessid" ;
+
+: cookie-session-id ( request -- id/f )
+    session-id-key get-cookie
+    dup [ value>> string>number ] when ;
+
+: post-session-id ( request -- id/f )
+    session-id-key swap post-data>> at string>number ;
+
+: request-session-id ( -- id/f )
+    request get dup method>> {
+        { "GET" [ cookie-session-id ] }
+        { "HEAD" [ cookie-session-id ] }
+        { "POST" [ post-session-id ] }
+    } case ;
+
+: request-session ( -- session/f )
+    request-session-id get-session ;
+
+: <session-cookie> ( id -- cookie )
+    session-id-key <cookie>
+        "$sessions" resolve-base-path >>path
+        sessions get timeout>> from-now >>expires
+        sessions get domain>> >>domain ;
+
+: put-session-cookie ( response -- response' )
+    session get id>> number>string <session-cookie> put-cookie ;
+
+M: sessions hidden-form-field ( responder -- )
+    drop
+    <input
+        "hidden" =type
+        session-id-key =name
+        session get id>> number>string =value
+    input/> ;
+
+M: sessions call-responder* ( path responder -- response )
+    sessions set
+    request-session [ begin-session ] unless*
+    existing-session put-session-cookie ;
+
+: logout-all-sessions ( uid -- )
+    session new swap >>uid delete-tuples ;
+
+M: sessions link-attr
+    drop
+    "session" optional-attr {
+        { "none" [ session off flow-id off ] }
+        { "current" [ ] }
+        { f [ ] }
+    } case ;
index 1a0f849a8fc9191cb84816f19d432c1951854dc7..90dc156ea63660cb73ad85b89d9aca775236457d 100644 (file)
@@ -11,7 +11,7 @@ html.components namespaces ;
 
 TUPLE: color red green blue ;
 
-[ ] [ 1 2 3 color boa from-tuple ] unit-test
+[ ] [ 1 2 3 color boa from-object ] unit-test
 
 [ 1 ] [ "red" value ] unit-test
 
@@ -107,7 +107,7 @@ TUPLE: color red green blue ;
 
 [ ] [ t "delivery" set-value ] unit-test
 
-[ "<input type='checkbox' name='delivery' selected='true'>Delivery</input>" ] [
+[ "<input type='checkbox' name='delivery' checked='true'>Delivery</input>" ] [
     [
         "delivery"
         <checkbox>
index efac730af6e9272d6685f707363901341119d0a4..c013007a144b114b58e45167190b2cf98ea3e363 100644 (file)
@@ -5,7 +5,7 @@ classes.tuple words arrays sequences sequences.lib splitting
 mirrors hashtables combinators continuations math strings
 fry locals calendar calendar.format xml.entities validators
 html.elements html.streams xmode.code2html farkup inspector
-lcs.diff2html ;
+lcs.diff2html urls ;
 IN: html.components
 
 SYMBOL: values
@@ -19,9 +19,9 @@ SYMBOL: values
 : prepare-value ( name object -- value name object )
     [ [ value ] keep ] dip ; inline
 
-: from-assoc ( assoc -- ) values get swap update ;
-
-: from-tuple ( tuple -- ) <mirror> from-assoc ;
+: from-object ( object -- )
+    dup assoc? [ <mirror> ] unless
+    values get swap update ;
 
 : deposit-values ( destination names -- )
     [ dup value ] H{ } map>assoc update ;
@@ -32,24 +32,19 @@ SYMBOL: values
 : with-each-index ( seq quot -- )
     '[
         [
-            blank-values 1+ "index" set-value @
+            values [ clone ] change
+            1+ "index" set-value @
         ] with-scope
     ] each-index ; inline
 
 : with-each-value ( seq quot -- )
     '[ "value" set-value @ ] with-each-index ; inline
 
-: with-each-assoc ( seq quot -- )
-    '[ from-assoc @ ] with-each-index ; inline
-
-: with-each-tuple ( seq quot -- )
-    '[ from-tuple @ ] with-each-index ; inline
-
-: with-assoc-values ( assoc quot -- )
-    '[ blank-values , from-assoc @ ] with-scope ; inline
+: with-each-object ( seq quot -- )
+    '[ from-object @ ] with-each-index ; inline
 
-: with-tuple-values ( assoc quot -- )
-    '[ blank-values , from-tuple @ ] with-scope ; inline
+: with-values ( object quot -- )
+    '[ blank-values , from-object @ ] with-scope ; inline
 
 : nest-values ( name quot -- )
     swap [
@@ -58,22 +53,6 @@ SYMBOL: values
         ] with-scope
     ] dip set-value ; inline
 
-: nest-tuple ( name quot -- )
-    swap [
-        [
-            H{ } clone [ <mirror> values set call ] keep
-        ] with-scope
-    ] dip set-value ; inline
-
-: object>string ( object -- string )
-    {
-        { [ dup real? ] [ number>string ] }
-        { [ dup timestamp? ] [ timestamp>string ] }
-        { [ dup string? ] [ ] }
-        { [ dup word? ] [ word-name ] }
-        { [ dup not ] [ drop "" ] }
-    } cond ;
-
 GENERIC: render* ( value name render -- )
 
 : render ( name renderer -- )
@@ -174,7 +153,7 @@ M: checkbox render*
     <input
         "checkbox" =type
         swap =name
-        swap [ "true" =selected ] when
+        swap [ "true" =checked ] when
     input>
         label>> escape-string write
     </input> ;
index e5377cedf8f168dfbb65d22817d0f5189a29135e..2b4920d4629295b1ae74e1b238685cd3439631ed 100644 (file)
@@ -4,7 +4,8 @@
 ! See http://factorcode.org/license.txt for BSD license.
 
 USING: io kernel namespaces prettyprint quotations
-sequences strings words xml.entities compiler.units effects ;
+sequences strings words xml.entities compiler.units effects
+urls math math.parser combinators calendar calendar.format ;
 
 IN: html.elements
 
@@ -126,11 +127,22 @@ SYMBOL: html
     dup def-for-html-word-<foo
     def-for-html-word-foo/> ;
 
+: object>string ( object -- string )
+    #! Should this be generic and in the core?
+    {
+        { [ dup real? ] [ number>string ] }
+        { [ dup timestamp? ] [ timestamp>string ] }
+        { [ dup url? ] [ url>string ] }
+        { [ dup string? ] [ ] }
+        { [ dup word? ] [ word-name ] }
+        { [ dup not ] [ drop "" ] }
+    } cond ;
+
 : write-attr ( value name -- )
     " " write-html
     write-html
     "='" write-html
-    escape-quoted-string write-html
+    object>string escape-quoted-string write-html
     "'" write-html ;
 
 : attribute-effect T{ effect f { "string" } 0 } ;
@@ -162,7 +174,7 @@ SYMBOL: html
     "id" "onclick" "style" "valign" "accesskey"
     "src" "language" "colspan" "onchange" "rel"
     "width" "selected" "onsubmit" "xmlns" "lang" "xml:lang"
-    "media" "title" "multiple"
+    "media" "title" "multiple" "checked"
 ] [ define-attribute-word ] each
 
 >>
index eaa0f0dc3dff310f00b96308024de5c91adafbae..6fb4429ea6217e5dd18f9a2550d65c7a4426cd16 100644 (file)
@@ -27,8 +27,7 @@ IN: html.templates.chloe.tests
 
 : test-template ( name -- template )
     "resource:extra/html/templates/chloe/test/"
-    swap
-    ".xml" 3append <chloe> ;
+    prepend <chloe> ;
 
 [ "Hello world" ] [
     [
@@ -156,6 +155,14 @@ TUPLE: person first-name last-name ;
 
 [ "<table><tr><td>RBaxter</td><td>Unknown</td></tr><tr><td>Doug</td><td>Coleman</td></tr></table>" ] [
     [
-        "test11" test-template call-template
+        "test10" test-template call-template
     ] run-template [ blank? not ] filter
 ] unit-test
+
+[ ] [ 1 "id" set-value ] unit-test
+
+[ "<a name=\"1\">Hello</a>" ] [
+    [
+        "test11" test-template call-template
+    ] run-template
+] unit-test
index 092f79bb36e7b11fed74f3186f3f77a855e07d83..93afa44d8139c6c5f81be0852318154595c8c383 100644 (file)
@@ -3,16 +3,12 @@
 USING: accessors kernel sequences combinators kernel namespaces
 classes.tuple assocs splitting words arrays memoize
 io io.files io.encodings.utf8 io.streams.string
-unicode.case tuple-syntax mirrors fry math
+unicode.case tuple-syntax mirrors fry math urls
 multiline xml xml.data xml.writer xml.utilities
 html.elements
 html.components
 html.templates
-http.server
-http.server.auth
-http.server.flows
-http.server.actions
-http.server.sessions ;
+html.templates.chloe.syntax ;
 IN: html.templates.chloe
 
 ! Chloe is Ed's favorite web designer
@@ -23,8 +19,6 @@ C: <chloe> chloe
 
 DEFER: process-template
 
-: chloe-ns "http://factorcode.org/chloe/1.0" ; inline
-
 : chloe-attrs-only ( assoc -- assoc' )
     [ drop name-url chloe-ns = ] assoc-filter ;
 
@@ -38,35 +32,22 @@ DEFER: process-template
         [ t ]
     } cond nip ;
 
-SYMBOL: tags
-
-MEMO: chloe-name ( string -- name )
-    name new
-        swap >>tag
-        chloe-ns >>url ;
-
-: required-attr ( tag name -- value )
-    dup chloe-name rot at*
-    [ nip ] [ drop " attribute is required" append throw ] if ;
-
-: optional-attr ( tag name -- value )
-    chloe-name swap at ;
-
 : process-tag-children ( tag -- )
     [ process-template ] each ;
 
+CHLOE: chloe process-tag-children ;
+
 : children>string ( tag -- string )
     [ process-tag-children ] with-string-writer ;
 
-: title-tag ( tag -- )
-    children>string set-title ;
+CHLOE: title children>string set-title ;
 
-: write-title-tag ( tag -- )
+CHLOE: write-title
     drop
     "head" tags get member? "title" tags get member? not and
     [ <title> write-title </title> ] [ write-title ] if ;
 
-: style-tag ( tag -- )
+CHLOE: style
     dup "include" optional-attr dup [
         swap children>string empty? [
             "style tag cannot have both an include attribute and a body" throw
@@ -76,146 +57,12 @@ MEMO: chloe-name ( string -- name )
         drop children>string
     ] if add-style ;
 
-: write-style-tag ( tag -- )
+CHLOE: write-style
     drop <style> write-style </style> ;
 
-: atom-tag ( tag -- )
-    [ "title" required-attr ]
-    [ "href" required-attr ]
-    bi set-atom-feed ;
-
-: write-atom-tag ( tag -- )
-    drop
-    "head" tags get member? [
-        write-atom-feed
-    ] [
-        atom-feed get value>> second write
-    ] if ;
-
-: parse-query-attr ( string -- assoc )
-    dup empty?
-    [ drop f ] [ "," split [ dup value ] H{ } map>assoc ] if ;
-
-: flow-attr ( tag -- )
-    "flow" optional-attr {
-        { "none" [ flow-id off ] }
-        { "begin" [ begin-flow ] }
-        { "current" [ ] }
-        { f [ ] }
-    } case ;
-
-: session-attr ( tag -- )
-    "session" optional-attr {
-        { "none" [ session off flow-id off ] }
-        { "current" [ ] }
-        { f [ ] }
-    } case ;
-
-: a-start-tag ( tag -- )
-    [
-        <a
-        dup flow-attr
-        dup session-attr
-        dup "value" optional-attr [ value f ] [
-            [ "href" required-attr ]
-            [ "query" optional-attr parse-query-attr ]
-            bi
-        ] ?if link>string =href
-        a>
-    ] with-scope ;
-
-: a-tag ( tag -- )
-    [ a-start-tag ]
-    [ process-tag-children ]
-    [ drop </a> ]
-    tri ;
-
-: form-start-tag ( tag -- )
-    [
-        [
-            <form
-            "POST" =method
-            {
-                [ flow-attr ]
-                [ session-attr ]
-                [ "action" required-attr resolve-base-path =action ]
-                [ tag-attrs non-chloe-attrs-only print-attrs ]
-            } cleave
-            form>
-        ] [
-            hidden-form-field
-            "for" optional-attr [ hidden render ] when*
-        ] bi
-    ] with-scope ;
-
-: form-tag ( tag -- )
-    [ form-start-tag ]
-    [ process-tag-children ]
-    [ drop </form> ]
-    tri ;
+CHLOE: even "index" value even? [ process-tag-children ] [ drop ] if ;
 
-DEFER: process-chloe-tag
-
-STRING: button-tag-markup
-<t:form class="inline" xmlns:t="http://factorcode.org/chloe/1.0">
-    <button type="submit"></button>
-</t:form>
-;
-
-: add-tag-attrs ( attrs tag -- )
-    tag-attrs swap update ;
-
-: button-tag ( tag -- )
-    button-tag-markup string>xml delegate
-    {
-        [ [ tag-attrs chloe-attrs-only ] dip add-tag-attrs ]
-        [ [ tag-attrs non-chloe-attrs-only ] dip "button" tag-named add-tag-attrs ]
-        [ [ children>string 1array ] dip "button" tag-named set-tag-children ]
-        [ nip ]
-    } 2cleave process-chloe-tag ;
-
-: attr>word ( value -- word/f )
-    dup ":" split1 swap lookup
-    [ ] [ "No such word: " swap append throw ] ?if ;
-
-: attr>var ( value -- word/f )
-    attr>word dup symbol? [
-        "Must be a symbol: " swap append throw
-    ] unless ;
-
-: if-satisfied? ( tag -- ? )
-    t swap
-    {
-        [ "code"  optional-attr [ attr>word execute and ] when* ]
-        [  "var"  optional-attr [ attr>var      get and ] when* ]
-        [ "svar"  optional-attr [ attr>var     sget and ] when* ]
-        [ "uvar"  optional-attr [ attr>var     uget and ] when* ]
-        [ "value" optional-attr [ value             and ] when* ]
-    } cleave ;
-
-: if-tag ( tag -- )
-    dup if-satisfied? [ process-tag-children ] [ drop ] if ;
-
-: even-tag ( tag -- )
-    "index" value even? [ process-tag-children ] [ drop ] if ;
-
-: odd-tag ( tag -- )
-    "index" value odd? [ process-tag-children ] [ drop ] if ;
-
-: (each-tag) ( tag quot -- )
-    [
-        [ "values" required-attr value ] keep
-        '[ , process-tag-children ]
-    ] dip call ; inline
-
-: each-tag ( tag -- )
-    [ with-each-value ] (each-tag) ;
-
-: each-tuple-tag ( tag -- )
-    [ with-each-tuple ] (each-tag) ;
-
-: each-assoc-tag ( tag -- )
-    [ with-each-assoc ] (each-tag) ;
+CHLOE: odd "index" value odd? [ process-tag-children ] [ drop ] if ;
 
 : (bind-tag) ( tag quot -- )
     [
@@ -223,83 +70,36 @@ STRING: button-tag-markup
         '[ , process-tag-children ]
     ] dip call ; inline
 
-: bind-tuple-tag ( tag -- )
-    [ with-tuple-values ] (bind-tag) ;
+CHLOE: each [ with-each-value ] (bind-tag) ;
 
-: bind-assoc-tag ( tag -- )
-    [ with-assoc-values ] (bind-tag) ;
+CHLOE: bind-each [ with-each-object ] (bind-tag) ;
+
+CHLOE: bind [ with-values ] (bind-tag) ;
 
 : error-message-tag ( tag -- )
     children>string render-error ;
 
-: validation-messages-tag ( tag -- )
-    drop render-validation-messages ;
+CHLOE: comment drop ;
 
-: singleton-component-tag ( tag class -- )
-    [ "name" required-attr ] dip render ;
+CHLOE: call-next-template drop call-next-template ;
 
-: attrs>slots ( tag tuple -- )
-    [ attrs>> ] [ <mirror> ] bi*
-    '[
-        swap tag>> dup "name" =
-        [ 2drop ] [ , set-at ] if
-    ] assoc-each ;
+CHLOE-SINGLETON: label
+CHLOE-SINGLETON: link
+CHLOE-SINGLETON: farkup
+CHLOE-SINGLETON: inspector
+CHLOE-SINGLETON: comparison
+CHLOE-SINGLETON: html
+CHLOE-SINGLETON: hidden
 
-: tuple-component-tag ( tag class -- )
-    [ drop "name" required-attr ]
-    [ new [ attrs>slots ] keep ]
-    2bi render ;
+CHLOE-TUPLE: field
+CHLOE-TUPLE: password
+CHLOE-TUPLE: choice
+CHLOE-TUPLE: checkbox
+CHLOE-TUPLE: code
 
 : process-chloe-tag ( tag -- )
-    dup name-tag {
-        { "chloe" [ process-tag-children ] }
-
-        ! HTML head
-        { "title" [ title-tag ] }
-        { "write-title" [ write-title-tag ] }
-        { "style" [ style-tag ] }
-        { "write-style" [ write-style-tag ] }
-        { "atom" [ atom-tag ] }
-        { "write-atom" [ write-atom-tag ] }
-
-        ! HTML elements
-        { "a" [ a-tag ] }
-        { "button" [ button-tag ] }
-
-        ! Components
-        { "label" [ label singleton-component-tag ] }
-        { "link" [ link singleton-component-tag ] }
-        { "code" [ code tuple-component-tag ] }
-        { "farkup" [ farkup singleton-component-tag ] }
-        { "inspector" [ inspector singleton-component-tag ] }
-        { "comparison" [ comparison singleton-component-tag ] }
-        { "html" [ html singleton-component-tag ] }
-
-        ! Forms
-        { "form" [ form-tag ] }
-        { "error-message" [ error-message-tag ] }
-        { "validation-messages" [ validation-messages-tag ] }
-        { "hidden" [ hidden singleton-component-tag ] }
-        { "field" [ field tuple-component-tag ] }
-        { "password" [ password tuple-component-tag ] }
-        { "textarea" [ textarea tuple-component-tag ] }
-        { "choice" [ choice tuple-component-tag ] }
-        { "checkbox" [ checkbox tuple-component-tag ] }
-
-        ! Control flow
-        { "if" [ if-tag ] }
-        { "even" [ even-tag ] }
-        { "odd" [ odd-tag ] }
-        { "each" [ each-tag ] }
-        { "each-assoc" [ each-assoc-tag ] }
-        { "each-tuple" [ each-tuple-tag ] }
-        { "bind-assoc" [ bind-assoc-tag ] }
-        { "bind-tuple" [ bind-tuple-tag ] }
-        { "comment" [ drop ] }
-        { "call-next-template" [ drop call-next-template ] }
-
-        [ "Unknown chloe tag: " prepend throw ]
-    } case ;
+    dup name-tag tags get at
+    [ call ] [ "Unknown chloe tag: " prepend throw ] ?if ;
 
 : process-tag ( tag -- )
     {
@@ -310,7 +110,15 @@ STRING: button-tag-markup
         [ drop tags get pop* ]
     } cleave ;
 
+: expand-attrs ( tag -- tag )
+    dup [ tag? ] is? [
+        clone [
+            [ "@" ?head [ value object>string ] when ] assoc-map
+        ] change-attrs
+    ] when ;
+
 : process-template ( xml -- )
+    expand-attrs
     {
         { [ dup [ chloe-tag? ] is? ] [ process-chloe-tag ] }
         { [ dup [ tag? ] is? ] [ process-tag ] }
@@ -334,6 +142,6 @@ STRING: button-tag-markup
     ] with-scope ;
 
 M: chloe call-template*
-    path>> utf8 <file-reader> read-xml process-chloe ;
+    path>> ".xml" append utf8 <file-reader> read-xml process-chloe ;
 
 INSTANCE: chloe template
diff --git a/extra/html/templates/chloe/syntax/syntax.factor b/extra/html/templates/chloe/syntax/syntax.factor
new file mode 100644 (file)
index 0000000..d30ddb9
--- /dev/null
@@ -0,0 +1,58 @@
+! Copyright (C) 2008 Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+IN: html.templates.chloe.syntax
+USING: accessors kernel sequences combinators kernel namespaces
+classes.tuple assocs splitting words arrays memoize parser
+io io.files io.encodings.utf8 io.streams.string
+unicode.case tuple-syntax mirrors fry math urls
+multiline xml xml.data xml.writer xml.utilities
+html.elements
+html.components
+html.templates ;
+
+SYMBOL: tags
+
+tags global [ H{ } clone or ] change-at
+
+: define-chloe-tag ( name quot -- ) tags get set-at ;
+
+: CHLOE:
+    scan parse-definition swap define-chloe-tag ;
+    parsing
+
+: chloe-ns "http://factorcode.org/chloe/1.0" ; inline
+
+MEMO: chloe-name ( string -- name )
+    name new
+        swap >>tag
+        chloe-ns >>url ;
+
+: required-attr ( tag name -- value )
+    dup chloe-name rot at*
+    [ nip ] [ drop " attribute is required" append throw ] if ;
+
+: optional-attr ( tag name -- value )
+    chloe-name swap at ;
+
+: singleton-component-tag ( tag class -- )
+    [ "name" required-attr ] dip render ;
+
+: CHLOE-SINGLETON:
+    scan dup '[ , singleton-component-tag ] define-chloe-tag ;
+    parsing
+
+: attrs>slots ( tag tuple -- )
+    [ attrs>> ] [ <mirror> ] bi*
+    '[
+        swap tag>> dup "name" =
+        [ 2drop ] [ , set-at ] if
+    ] assoc-each ;
+
+: tuple-component-tag ( tag class -- )
+    [ drop "name" required-attr ]
+    [ new [ attrs>slots ] keep ]
+    2bi render ;
+
+: CHLOE-TUPLE:
+    scan dup '[ , tuple-component-tag ] define-chloe-tag ;
+    parsing
index afded9366fd992698d59b1ce5f3271b0bccbc425..fd4a64ad0ae6a870df0e5dcf4083beb0810ca28c 100644 (file)
@@ -3,12 +3,12 @@
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
        <table>
-               <t:each-tuple t:values="people">
+               <t:bind-each t:name="people">
                        <tr>
                                <td><t:label t:name="first-name"/></td>
                                <td><t:label t:name="last-name"/></td>
                        </tr>
-               </t:each-tuple>
+               </t:bind-each>
        </table>
 
 </t:chloe>
index 17e31b1a596429a6801fed4be44fada651531d24..a9b2769445ca17a71047338e141c4348b68f87bf 100644 (file)
@@ -1,14 +1,3 @@
 <?xml version='1.0' ?>
 
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-       <table>
-               <t:each-assoc t:values="people">
-                       <tr>
-                               <td><t:label t:name="first-name"/></td>
-                               <td><t:label t:name="last-name"/></td>
-                       </tr>
-               </t:each-assoc>
-       </table>
-
-</t:chloe>
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0"><a name="@id">Hello</a></t:chloe>
index bcfc468738326db7c21519aa0546110ec19516eb..6166c800eddbe2cb4893e2aa7b927df30e7f6ce0 100644 (file)
@@ -3,7 +3,7 @@
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
        <ul>
-               <t:each t:values="numbers">
+               <t:each t:name="numbers">
                        <li><t:label t:name="value"/></li>
                </t:each>
        </ul>
index 580af58ecc96ac8b7c92dedea62cc908bf7a1bb8..de774f0864d1c29846e95bbb132610027499fcf3 100644 (file)
@@ -2,7 +2,8 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: accessors kernel fry io io.encodings.utf8 io.files
 debugger prettyprint continuations namespaces boxes sequences
-arrays strings html.elements io.streams.string quotations ;
+arrays strings html.elements io.streams.string
+quotations xml.data xml.writer ;
 IN: html.templates
 
 MIXIN: template
@@ -13,6 +14,8 @@ M: string call-template* write ;
 
 M: callable call-template* call ;
 
+M: xml call-template* write-xml ;
+
 M: object call-template* output-stream get stream-copy ;
 
 ERROR: template-error template error ;
@@ -43,17 +46,17 @@ SYMBOL: style
 : write-style ( -- )
     style get >string write ;
 
-SYMBOL: atom-feed
+SYMBOL: atom-feeds
 
-: set-atom-feed ( title url -- )
-    2array atom-feed get >box ;
+: add-atom-feed ( title url -- )
+    2array atom-feeds get push ;
 
-: write-atom-feed ( -- )
-    atom-feed get value>> [
+: write-atom-feeds ( -- )
+    atom-feeds get [
         <link "alternate" =rel "application/atom+xml" =type
-        [ first =title ] [ second =href ] bi
+        first2 [ =title ] [ =href ] bi*
         link/>
-    ] when* ;
+    ] each ;
 
 SYMBOL: nested-template?
 
@@ -66,9 +69,9 @@ M: f call-template* drop call-next-template ;
 
 : with-boilerplate ( body template -- )
     [
-        title get [ <box> title set ] unless
-        atom-feed get [ <box> atom-feed set ] unless
-        style get [ SBUF" " clone style set ] unless
+        title [ <box> or ] change
+        style [ SBUF" " clone or ] change
+        atom-feeds [ V{ } like ] change
 
         [
             [
index db90f746acffeddd8a09494c2d7bf15897188dd8..7ce066f0d76dbf602384065dafb390ce6403d848 100755 (executable)
@@ -1,5 +1,5 @@
 USING: http.client http.client.private http tools.test
-tuple-syntax namespaces ;
+tuple-syntax namespaces urls ;
 [ "localhost" f ] [ "localhost" parse-host ] unit-test
 [ "localhost" 8888 ] [ "localhost:8888" parse-host ] unit-test
 
@@ -10,11 +10,8 @@ tuple-syntax namespaces ;
 
 [
     TUPLE{ request
-        protocol: http
+        url: TUPLE{ url protocol: "http" host: "www.apple.com" port: 80 path: "/index.html" query: H{ } }
         method: "GET"
-        host: "www.apple.com"
-        port: 80
-        path: "/index.html"
         version: "1.1"
         cookies: V{ }
         header: H{ { "connection" "close" } { "user-agent" "Factor http.client vocabulary" } }
@@ -28,11 +25,8 @@ tuple-syntax namespaces ;
 
 [
     TUPLE{ request
-        protocol: https
+        url: TUPLE{ url protocol: "https" host: "www.amazon.com" port: 443 path: "/index.html" query: H{ } }
         method: "GET"
-        host: "www.amazon.com"
-        port: 443
-        path: "/index.html"
         version: "1.1"
         cookies: V{ }
         header: H{ { "connection" "close" } { "user-agent" "Factor http.client vocabulary" } }
index 7b156a4b9b2f76135687ac5cf3c832c892ad6256..9fd5f15d6a71a7f80e0deda1d06ef1a36cb185fe 100755 (executable)
@@ -27,8 +27,7 @@ SYMBOL: redirects
         redirects inc
         redirects get max-redirects < [
             request get
-            swap "location" header dup absolute-url?
-            [ request-with-url ] [ request-with-path ] if
+            swap "location" header request-with-url
             "GET" >>method http-request
         ] [
             too-many-redirects
@@ -51,7 +50,7 @@ PRIVATE>
 
 : http-request ( request -- response data )
     dup request [
-        dup request-addr latin1 [
+        dup url>> url-addr latin1 [
             1 minutes timeouts
             write-request
             read-response
index 151d1ce84f2cd4e12dd0bc96bf1cf3a866f78be8..5a11814f09aaac29e276ffa1748c29a1e5759143 100755 (executable)
@@ -1,37 +1,13 @@
 USING: http tools.test multiline tuple-syntax
 io.streams.string kernel arrays splitting sequences
-assocs io.sockets db db.sqlite continuations ;
+assocs io.sockets db db.sqlite continuations urls ;
 IN: http.tests
 
-[ "hello%20world" ] [ "hello world" url-encode ] unit-test
-[ "hello world" ] [ "hello%20world" url-decode ] unit-test
-[ "~hello world" ] [ "%7ehello+world" url-decode ] unit-test
-[ f ] [ "%XX%XX%XX" url-decode ] unit-test
-[ f ] [ "%XX%XX%X" url-decode ] unit-test
-
-[ "hello world"   ] [ "hello+world"    url-decode ] unit-test
-[ "hello world"   ] [ "hello%20world"  url-decode ] unit-test
-[ " ! "           ] [ "%20%21%20"      url-decode ] unit-test
-[ "hello world"   ] [ "hello world%"   url-decode ] unit-test
-[ "hello world"   ] [ "hello world%x"  url-decode ] unit-test
-[ "hello%20world" ] [ "hello world"    url-encode ] unit-test
-[ "%20%21%20"     ] [ " ! "            url-encode ] unit-test
-
-[ "\u001234hi\u002045" ] [ "\u001234hi\u002045" url-encode url-decode ] unit-test
-
 [ "/" ] [ "http://foo.com" url>path ] unit-test
 [ "/" ] [ "http://foo.com/" url>path ] unit-test
 [ "/bar" ] [ "http://foo.com/bar" url>path ] unit-test
 [ "/bar" ] [ "/bar" url>path ] unit-test
 
-[ "a=b&a=c" ] [ { { "a" { "b" "c" } } } assoc>query ] unit-test
-
-[ H{ { "a" "b" } } ] [ "a=b" query>assoc ] unit-test
-
-[ H{ { "a" { "b" "c" } } } ] [ "a=b&a=c" query>assoc ] unit-test
-
-[ "a=3" ] [ { { "a" 3 } } assoc>query ] unit-test
-
 : lf>crlf "\n" split "\r\n" join ;
 
 STRING: read-request-test-1
@@ -45,11 +21,8 @@ blah
 
 [
     TUPLE{ request
-        protocol: http
-        port: 80
+        url: TUPLE{ url protocol: "http" port: 80 path: "/bar" }
         method: "GET"
-        path: "/bar"
-        query: H{ }
         version: "1.1"
         header: H{ { "some-header" "1; 2" } { "content-length" "4" } }
         post-data: "blah"
@@ -85,14 +58,10 @@ Host: www.sex.com
 
 [
     TUPLE{ request
-        protocol: http
-        port: 80
+        url: TUPLE{ url protocol: "http" port: 80 host: "www.sex.com" path: "/bar" }
         method: "HEAD"
-        path: "/bar"
-        query: H{ }
         version: "1.1"
         header: H{ { "host" "www.sex.com" } }
-        host: "www.sex.com"
         cookies: V{ }
     }
 ] [
@@ -101,6 +70,15 @@ Host: www.sex.com
     ] with-string-reader
 ] unit-test
 
+STRING: read-request-test-3
+GET nested HTTP/1.0
+
+;
+
+[ read-request-test-3 [ read-request ] with-string-reader ]
+[ "Bad request: URL" = ]
+must-fail-with
+
 STRING: read-response-test-1
 HTTP/1.1 404 not found
 Content-Type: text/html; charset=UTF8
@@ -145,14 +123,14 @@ read-response-test-1' 1array [
 ] unit-test
 
 ! Live-fire exercise
-USING: http.server http.server.static http.server.sessions
-http.server.actions http.server.auth.login http.server.db http.client
+USING: http.server http.server.static furnace.sessions
+furnace.actions furnace.auth.login furnace.db http.client
 io.server io.files io io.encodings.ascii
 accessors namespaces threads ;
 
 : add-quit-action
     <action>
-        [ stop-server [ "Goodbye" write ] <html-content> ] >>display
+        [ stop-server "Goodbye" "text/html" <content> ] >>display
     "quit" add-responder ;
 
 : test-db "test.db" temp-file sqlite-db ;
@@ -171,7 +149,7 @@ test-db [
                 "resource:extra/http/test" <static> >>default
             "nested" add-responder
             <action>
-                [ "redirect-loop" f <standard-redirect> ] >>display
+                [ URL" redirect-loop" <redirect> ] >>display
             "redirect-loop" add-responder
         main-responder set
 
@@ -186,16 +164,6 @@ test-db [
     "http://localhost:1237/nested/foo.html" http-get =
 ] unit-test
 
-! Try with a slightly malformed request
-[ t ] [
-    "localhost" 1237 <inet> ascii [
-        "GET nested HTTP/1.0\r\n" write flush
-        "\r\n" write flush
-        read-crlf drop
-        read-header
-    ] with-client "location" swap at "/" head?
-] unit-test
-
 [ "http://localhost:1237/redirect-loop" http-get ]
 [ too-many-redirects? ] must-fail-with
 
@@ -237,7 +205,7 @@ test-db [
 [ ] [
     [
         <dispatcher>
-            <action> [ [ "Hi" write ] <text-content> ] >>display
+            <action> [ [ "Hi" write ] "text/plain" <content> ] >>display
             <login>
             <sessions>
             "" add-responder
index 89c8f62d5c3d86fb6333578810ba43ddf269c428..a4e6451044868203eb325d7c0888905b2c00b149 100755 (executable)
@@ -7,88 +7,31 @@ strings vectors hashtables quotations arrays byte-arrays
 math.parser calendar calendar.format
 
 io io.streams.string io.encodings.utf8 io.encodings.string
-io.sockets io.sockets.secure
+io.sockets io.sockets.secure io.server
 
 unicode.case unicode.categories qualified
 
-html.templates ;
+urls html.templates ;
 
 EXCLUDE: fry => , ;
 
 IN: http
 
-SINGLETON: http
+: secure-protocol? ( protocol -- ? )
+    "https" = ;
 
-SINGLETON: https
+: url-addr ( url -- addr )
+    [ [ host>> ] [ port>> ] bi <inet> ] [ protocol>> ] bi
+    secure-protocol? [ <secure> ] when ;
 
-GENERIC: http-port ( protocol -- port )
-
-M: http http-port drop 80 ;
-
-M: https http-port drop 443 ;
-
-GENERIC: protocol>string ( protocol -- string )
-
-M: http protocol>string drop "http" ;
-
-M: https protocol>string drop "https" ;
-
-: string>protocol ( string -- protocol )
+: protocol-port ( protocol -- port )
     {
-        { "http" [ http ] }
-        { "https" [ https ] }
-        [ "Unknown protocol: " swap append throw ]
+        { "http" [ 80 ] }
+        { "https" [ 443 ] }
     } case ;
 
-: absolute-url? ( url -- ? )
-    [ "http://" head? ] [ "https://" head? ] bi or ;
-
-: url-quotable? ( ch -- ? )
-    #! In a URL, can this character be used without
-    #! URL-encoding?
-    {
-        { [ dup letter? ] [ t ] }
-        { [ dup LETTER? ] [ t ] }
-        { [ dup digit? ] [ t ] }
-        { [ dup "/_-.:" member? ] [ t ] }
-        [ f ]
-    } cond nip ; foldable
-
-: push-utf8 ( ch -- )
-    1string utf8 encode
-    [ CHAR: % , >hex 2 CHAR: 0 pad-left % ] each ;
-
-: url-encode ( str -- str )
-    [
-        [ dup url-quotable? [ , ] [ push-utf8 ] if ] each
-    ] "" make ;
-
-: url-decode-hex ( index str -- )
-    2dup length 2 - >= [
-        2drop
-    ] [
-        [ 1+ dup 2 + ] dip subseq  hex> [ , ] when*
-    ] if ;
-
-: url-decode-% ( index str -- index str )
-    2dup url-decode-hex [ 3 + ] dip ;
-
-: url-decode-+-or-other ( index str ch -- index str )
-    dup CHAR: + = [ drop CHAR: \s ] when , [ 1+ ] dip ;
-
-: url-decode-iter ( index str -- )
-    2dup length >= [
-        2drop
-    ] [
-        2dup nth dup CHAR: % = [
-            drop url-decode-%
-        ] [
-            url-decode-+-or-other
-        ] if url-decode-iter
-    ] if ;
-
-: url-decode ( str -- str )
-    [ 0 swap url-decode-iter ] "" make utf8 decode ;
+: ensure-port ( url -- url' )
+    dup protocol>> '[ , protocol-port or ] change-port ;
 
 : crlf "\r\n" write ;
 
@@ -130,6 +73,7 @@ M: https protocol>string drop "https" ;
     {
         { [ dup number? ] [ number>string ] }
         { [ dup timestamp? ] [ timestamp>http-string ] }
+        { [ dup url? ] [ url>string ] }
         { [ dup string? ] [ ] }
         { [ dup sequence? ] [ [ header-value>string ] map "; " join ] }
     } cond ;
@@ -145,42 +89,6 @@ M: https protocol>string drop "https" ;
         header-value>string check-header-string write crlf
     ] assoc-each crlf ;
 
-: add-query-param ( value key assoc -- )
-    [
-        at [
-            {
-                { [ dup string? ] [ swap 2array ] }
-                { [ dup array? ] [ swap suffix ] }
-                { [ dup not ] [ drop ] }
-            } cond
-        ] when*
-    ] 2keep set-at ;
-
-: query>assoc ( query -- assoc )
-    dup [
-        "&" split H{ } clone [
-            [
-                [ "=" split1 [ dup [ url-decode ] when ] bi@ swap ] dip
-                add-query-param
-            ] curry each
-        ] keep
-    ] when ;
-
-: assoc>query ( hash -- str )
-    [
-        {
-            { [ dup number? ] [ number>string 1array ] }
-            { [ dup string? ] [ 1array ] }
-            { [ dup sequence? ] [ ] }
-        } cond
-    ] assoc-map
-    [
-        [
-            [ url-encode ] dip
-            [ url-encode "=" swap 3append , ] with each
-        ] assoc-each
-    ] { } make "&" join ;
-
 TUPLE: cookie name value path domain expires max-age http-only ;
 
 : <cookie> ( value name -- cookie )
@@ -236,12 +144,8 @@ TUPLE: cookie name value path domain expires max-age http-only ;
     [ unparse-cookie ] map concat "; " join ;
 
 TUPLE: request
-protocol
-host
-port
 method
-path
-query
+url
 version
 header
 post-data
@@ -254,19 +158,15 @@ cookies ;
 : <request>
     request new
         "1.1" >>version
-        http >>protocol
+        <url>
+            "http" >>protocol
+            H{ } clone >>query
+        >>url
         H{ } clone >>header
-        H{ } clone >>query
         V{ } clone >>cookies
         "close" "connection" set-header
         "Factor http.client vocabulary" "user-agent" set-header ;
 
-: query-param ( request key -- value )
-    swap query>> at ;
-
-: set-query-param ( request value key -- request )
-    pick query>> set-at ;
-
 : chop-hostname ( str -- str' )
     ":" split1 "//" ?head drop nip
     CHAR: / over index over length or tail
@@ -284,21 +184,17 @@ cookies ;
     " " read-until [ "Bad request: method" throw ] unless
     >>method ;
 
-: read-query ( request -- request )
-    " " read-until
-    [ "Bad request: query params" throw ] unless
-    query>assoc >>query ;
+: check-absolute ( url -- url )
+    dup path>> "/" head? [ "Bad request: URL" throw ] unless ; inline
 
 : read-url ( request -- request )
-    " ?" read-until {
-        { CHAR: \s [ dup empty? [ drop read-url ] [ url>path >>path ] if ] }
-        { CHAR: ? [ url>path >>path read-query ] }
-        [ "Bad request: URL" throw ]
-    } case ;
+    " " read-until [
+        dup empty? [ drop read-url ] [ >url check-absolute >>url ] if
+    ] [ "Bad request: URL" throw ] if ;
 
 : parse-version ( string -- version )
-    "HTTP/" ?head [ "Bad version" throw ] unless
-    dup { "1.0" "1.1" } member? [ "Bad version" throw ] unless ;
+    "HTTP/" ?head [ "Bad request: version" throw ] unless
+    dup { "1.0" "1.1" } member? [ "Bad request: version" throw ] unless ;
 
 : read-request-version ( request -- request )
     read-crlf [ CHAR: \s = ] left-trim
@@ -325,13 +221,11 @@ SYMBOL: max-post-request
 : read-post-data ( request -- request )
     dup header>> content-length [ read >>post-data ] when* ;
 
-: parse-host ( string -- host port )
-    "." ?tail drop ":" split1
-    dup [ string>number ] when ;
-
 : extract-host ( request -- request )
-    dup [ "host" header parse-host ] keep protocol>> http-port or
-    [ >>host ] [ >>port ] bi* ;
+    [ ] [ url>> ] [ "host" header parse-host ] tri
+    [ >>host ] [ >>port ] bi*
+    ensure-port
+    drop ;
 
 : extract-post-data-type ( request -- request )
     dup "content-type" header >>post-data-type ;
@@ -349,6 +243,9 @@ SYMBOL: max-post-request
 : parse-content-type ( content-type -- type encoding )
     ";" split1 parse-content-type-attributes "charset" swap at ;
 
+: detect-protocol ( request -- request )
+    dup url>> remote-address get secure? "https" "http" ? >>protocol drop ;
+
 : read-request ( -- request )
     <request>
     read-method
@@ -356,6 +253,7 @@ SYMBOL: max-post-request
     read-request-version
     read-request-header
     read-post-data
+    detect-protocol
     extract-host
     extract-post-data-type
     parse-post-data
@@ -364,15 +262,8 @@ SYMBOL: max-post-request
 : write-method ( request -- request )
     dup method>> write bl ;
 
-: (link>string) ( url query -- url' )
-    [ url-encode ] [ assoc>query ] bi*
-    dup empty? [ drop ] [ "?" swap 3append ] if ;
-
-: write-url ( request -- )
-    [ path>> ] [ query>> ] bi (link>string) write ;
-
 : write-request-url ( request -- request )
-    dup write-url bl ;
+    dup url>> relative-url url>string write bl ;
 
 : write-version ( request -- request )
     "HTTP/" write dup request-version write crlf ;
@@ -383,24 +274,13 @@ SYMBOL: max-post-request
         "application/x-www-form-urlencoded" >>post-data-type
     ] if ;
 
-GENERIC: protocol-addr ( request protocol -- addr )
-
-M: object protocol-addr
-    drop [ host>> ] [ port>> ] bi <inet> ;
-
-M: https protocol-addr
-    call-next-method <secure> ;
-
-: request-addr ( request -- addr )
-    dup protocol>> protocol-addr ;
-
-: request-host ( request -- string )
-    [ host>> ] [ port>> ] bi dup http http-port =
+: url-host ( url -- string )
+    [ host>> ] [ port>> ] bi dup "http" protocol-port =
     [ drop ] [ ":" swap number>string 3append ] if ;
 
 : write-request-header ( request -- request )
     dup header>> >hashtable
-    over host>> [ over request-host "host" pick set-at ] when
+    over url>> host>> [ over url>> url-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*
@@ -419,38 +299,8 @@ M: https protocol-addr
     flush
     drop ;
 
-: request-with-path ( request path -- request )
-    [ "/" prepend ] [ "/" ] if*
-    "?" split1 [ >>path ] [ dup [ query>assoc ] when >>query ] bi* ;
-
 : request-with-url ( request url -- request )
-    ":" split1
-    [ string>protocol >>protocol ]
-    [
-        "//" ?head [ "Invalid URL" throw ] unless
-        "/" split1
-        [
-            parse-host [ >>host ] [ >>port ] bi*
-            dup protocol>> http-port '[ , or ] change-port
-        ]
-        [ request-with-path ]
-        bi*
-    ] bi* ;
-
-: request-url ( request -- url )
-    [
-        [
-            dup host>> [
-                [ protocol>> protocol>string write "://" write ]
-                [ host>> url-encode write ":" write ]
-                [ [ port>> ] [ protocol>> http-port or ] bi number>string write ]
-                tri
-            ] [ drop ] if
-        ]
-        [ path>> "/" head? [ "/" write ] unless ]
-        [ write-url ]
-        tri
-    ] with-string-writer ;
+    '[ , >url derive-url ensure-port ] change-url ;
 
 GENERIC: write-response ( response -- )
 
diff --git a/extra/http/server/actions/actions-tests.factor b/extra/http/server/actions/actions-tests.factor
deleted file mode 100755 (executable)
index 480cbc8..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-USING: kernel http.server.actions validators
-tools.test math math.parser multiline namespaces http
-io.streams.string http.server sequences splitting accessors ;
-IN: http.server.actions.tests
-
-<action>
-    [ "a" param "b" param [ string>number ] bi@ + ] >>display
-"action-1" set
-
-: lf>crlf "\n" split "\r\n" join ;
-
-STRING: action-request-test-1
-GET http://foo/bar?a=12&b=13 HTTP/1.1
-
-blah
-;
-
-[ 25 ] [
-    init-request
-    action-request-test-1 lf>crlf
-    [ read-request ] with-string-reader
-    request set
-    { } "action-1" get call-responder
-] unit-test
diff --git a/extra/http/server/actions/actions.factor b/extra/http/server/actions/actions.factor
deleted file mode 100755 (executable)
index eb5b8bf..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-! Copyright (C) 2008 Slava Pestov.\r
-! See http://factorcode.org/license.txt for BSD license.\r
-USING: accessors sequences kernel assocs combinators http.server\r
-validators http hashtables namespaces fry continuations locals\r
-boxes xml.entities html.elements html.components io arrays math ;\r
-IN: http.server.actions\r
-\r
-SYMBOL: params\r
-\r
-SYMBOL: rest-param\r
-\r
-: render-validation-messages ( -- )\r
-    validation-messages get\r
-    dup empty? [ drop ] [\r
-        <ul "errors" =class ul>\r
-            [ <li> message>> escape-string write </li> ] each\r
-        </ul>\r
-    ] if ;\r
-\r
-TUPLE: action rest-param init display validate submit ;\r
-\r
-: new-action ( class -- action )\r
-    new\r
-        [ ] >>init\r
-        [ <400> ] >>display\r
-        [ ] >>validate\r
-        [ <400> ] >>submit ;\r
-\r
-: <action> ( -- action )\r
-    action new-action ;\r
-\r
-: handle-get ( action -- response )\r
-    blank-values\r
-    [ init>> call ]\r
-    [ display>> call ]\r
-    bi ;\r
-\r
-: validation-failed ( -- * )\r
-    request get method>> "POST" =\r
-    [ action get display>> call ] [ <400> ] if exit-with ;\r
-\r
-: handle-post ( action -- response )\r
-    init-validation\r
-    blank-values\r
-    [ validate>> call ]\r
-    [ submit>> call ] bi ;\r
-\r
-: handle-rest-param ( arg -- )\r
-    dup length 1 > action get rest-param>> not or\r
-    [ <404> exit-with ] [\r
-        action get rest-param>> associate rest-param set\r
-    ] if ;\r
-\r
-M: action call-responder* ( path action -- response )\r
-    dup action set\r
-    '[\r
-        , dup empty? [ drop ] [ handle-rest-param ] if\r
-\r
-        init-validation\r
-        ,\r
-        request get\r
-        [ request-params rest-param get assoc-union params set ]\r
-        [ method>> ] bi\r
-        {\r
-            { "GET" [ handle-get ] }\r
-            { "HEAD" [ handle-get ] }\r
-            { "POST" [ handle-post ] }\r
-        } case\r
-    ] with-exit-continuation ;\r
-\r
-: param ( name -- value )\r
-    params get at ;\r
-\r
-: check-validation ( -- )\r
-    validation-failed? [ validation-failed ] when ;\r
-\r
-: validate-params ( validators -- )\r
-    params get swap validate-values from-assoc\r
-    check-validation ;\r
-\r
-: validate-integer-id ( -- )\r
-    { { "id" [ v-number ] } } validate-params ;\r
-\r
-TUPLE: page-action < action template ;\r
-\r
-: <page-action> ( -- page )\r
-    page-action new-action\r
-        dup '[ , template>> <html-content> ] >>display ;\r
-\r
-TUPLE: feed-action < action feed ;\r
-\r
-: <feed-action> ( -- feed )\r
-    feed-action new\r
-        dup '[ , feed>> call <feed-content> ] >>display ;\r
diff --git a/extra/http/server/auth/auth.factor b/extra/http/server/auth/auth.factor
deleted file mode 100755 (executable)
index 4b34fbe..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-! Copyright (c) 2008 Slava Pestov\r
-! See http://factorcode.org/license.txt for BSD license.\r
-USING: accessors assocs namespaces kernel sequences sets\r
-http.server\r
-http.server.sessions\r
-http.server.auth.providers ;\r
-IN: http.server.auth\r
-\r
-SYMBOL: logged-in-user\r
-\r
-GENERIC: init-user-profile ( responder -- )\r
-\r
-M: object init-user-profile drop ;\r
-\r
-M: dispatcher init-user-profile\r
-    default>> init-user-profile ;\r
-\r
-M: filter-responder init-user-profile\r
-    responder>> init-user-profile ;\r
-\r
-: profile ( -- assoc ) logged-in-user get profile>> ;\r
-\r
-: user-changed ( -- )\r
-    logged-in-user get t >>changed? drop ;\r
-\r
-: uget ( key -- value )\r
-    profile at ;\r
-\r
-: uset ( value key -- )\r
-    profile set-at\r
-    user-changed ;\r
-\r
-: uchange ( quot key -- )\r
-    profile swap change-at\r
-    user-changed ; inline\r
-\r
-SYMBOL: capabilities\r
-\r
-V{ } clone capabilities set-global\r
-\r
-: define-capability ( word -- ) capabilities get adjoin ;\r
diff --git a/extra/http/server/auth/basic/basic.factor b/extra/http/server/auth/basic/basic.factor
deleted file mode 100755 (executable)
index ff071b3..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-! Copyright (c) 2007 Chris Double.\r
-! See http://factorcode.org/license.txt for BSD license.\r
-USING: accessors quotations assocs kernel splitting\r
-base64 html.elements io combinators http.server\r
-http.server.auth.providers http.server.auth.login\r
-http sequences ;\r
-IN: http.server.auth.basic\r
-\r
-TUPLE: basic-auth < filter-responder realm provider ;\r
-\r
-C: <basic-auth> basic-auth\r
-\r
-: authorization-ok? ( provider header -- ? )\r
-    #! Given the realm and the 'Authorization' header,\r
-    #! authenticate the user.\r
-    dup [\r
-        " " split1 swap "Basic" = [\r
-            base64> ":" split1 spin check-login\r
-        ] [\r
-            2drop f\r
-        ] if\r
-    ] [\r
-        2drop f\r
-    ] if ;\r
-\r
-: <401> ( realm -- response )\r
-    401 "Unauthorized" <trivial-response>\r
-    "Basic realm=\"" rot "\"" 3append\r
-    "WWW-Authenticate" set-header\r
-    [\r
-        <html> <body>\r
-            "Username or Password is invalid" write\r
-        </body> </html>\r
-    ] >>body ;\r
-\r
-: logged-in? ( request responder -- ? )\r
-    provider>> swap "authorization" header authorization-ok? ;\r
-\r
-M: basic-auth call-responder* ( request path responder -- response )\r
-    pick over logged-in?\r
-    [ call-next-method ] [ 2nip realm>> <401> ] if ;\r
diff --git a/extra/http/server/auth/login/boilerplate.xml b/extra/http/server/auth/login/boilerplate.xml
deleted file mode 100644 (file)
index edc8c32..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-       <h1><t:write-title /></h1>
-
-       <t:call-next-template />
-
-</t:chloe>
diff --git a/extra/http/server/auth/login/edit-profile.xml b/extra/http/server/auth/login/edit-profile.xml
deleted file mode 100644 (file)
index 6beaf5d..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-       <t:title>Edit Profile</t:title>
-
-       <t:form t:action="$login/edit-profile">
-
-       <table>
-       
-       <tr>
-               <th class="field-label">User name:</th>
-               <td><t:label t:name="username" /></td>
-       </tr>
-       
-       <tr>
-               <th class="field-label">Real name:</th>
-               <td><t:field t:name="realname" /></td>
-       </tr>
-       
-       <tr>
-               <td></td>
-               <td>Specifying a real name is optional.</td>
-       </tr>
-       
-       <tr>
-               <th class="field-label">Current password:</th>
-               <td><t:password t:name="password" /></td>
-       </tr>
-       
-       <tr>
-               <td></td>
-               <td>If you don't want to change your current password, leave this field blank.</td>
-       </tr>
-       
-       <tr>
-               <th class="field-label">New password:</th>
-               <td><t:password t:name="new-password" /></td>
-       </tr>
-       
-       <tr>
-               <th class="field-label">Verify:</th>
-               <td><t:password t:name="verify-password" /></td>
-       </tr>
-       
-       <tr>
-               <td></td>
-               <td>If you are changing your password, enter it twice to ensure it is correct.</td>
-       </tr>
-       
-       <tr>
-               <th class="field-label">E-mail:</th>
-               <td><t:field t:name="email" /></td>
-       </tr>
-       
-       <tr>
-               <td></td>
-               <td>Specifying an e-mail address is optional. It enables the "recover password" feature.</td>
-       </tr>
-       
-       </table>
-
-       <p>
-               <input type="submit" value="Update" />
-               <t:validation-messages />
-       </p>
-
-       </t:form>
-       
-</t:chloe>
diff --git a/extra/http/server/auth/login/login-tests.factor b/extra/http/server/auth/login/login-tests.factor
deleted file mode 100755 (executable)
index b69630a..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-IN: http.server.auth.login.tests\r
-USING: tools.test http.server.auth.login ;\r
-\r
-\ <login> must-infer\r
-\ allow-registration must-infer\r
-\ allow-password-recovery must-infer\r
diff --git a/extra/http/server/auth/login/login.factor b/extra/http/server/auth/login/login.factor
deleted file mode 100755 (executable)
index fd4fbab..0000000
+++ /dev/null
@@ -1,353 +0,0 @@
-! Copyright (c) 2008 Slava Pestov\r
-! See http://factorcode.org/license.txt for BSD license.\r
-USING: accessors quotations assocs kernel splitting\r
-combinators sequences namespaces hashtables sets\r
-fry arrays threads qualified random validators\r
-io\r
-io.sockets\r
-io.encodings.utf8\r
-io.encodings.string\r
-io.binary\r
-continuations\r
-destructors\r
-checksums\r
-checksums.sha2\r
-validators\r
-html.components\r
-html.elements\r
-html.templates\r
-html.templates.chloe\r
-http\r
-http.server\r
-http.server.auth\r
-http.server.auth.providers\r
-http.server.auth.providers.db\r
-http.server.actions\r
-http.server.flows\r
-http.server.sessions\r
-http.server.boilerplate ;\r
-QUALIFIED: smtp\r
-IN: http.server.auth.login\r
-\r
-TUPLE: login < dispatcher users checksum ;\r
-\r
-: users ( -- provider )\r
-    login get users>> ;\r
-\r
-: encode-password ( string salt -- bytes )\r
-    [ utf8 encode ] [ 4 >be ] bi* append\r
-    login get checksum>> checksum-bytes ;\r
-\r
-: >>encoded-password ( user string -- user )\r
-    32 random-bits [ encode-password ] keep\r
-    [ >>password ] [ >>salt ] bi* ; inline\r
-\r
-: valid-login? ( password user -- ? )\r
-    [ salt>> encode-password ] [ password>> ] bi = ;\r
-\r
-: check-login ( password username -- user/f )\r
-    users get-user dup [ [ valid-login? ] keep and ] [ 2drop f ] if ;\r
-\r
-! Destructor\r
-TUPLE: user-saver user ;\r
-\r
-C: <user-saver> user-saver\r
-\r
-M: user-saver dispose\r
-    user>> dup changed?>> [ users update-user ] [ drop ] if ;\r
-\r
-: save-user-after ( user -- )\r
-    <user-saver> &dispose drop ;\r
-\r
-: login-template ( name -- template )\r
-    "resource:extra/http/server/auth/login/" swap ".xml"\r
-    3append <chloe> ;\r
-\r
-! ! ! Login\r
-: successful-login ( user -- response )\r
-    username>> set-uid "$login" end-flow ;\r
-\r
-: login-failed ( -- * )\r
-    "invalid username or password" validation-error\r
-    validation-failed ;\r
-\r
-: <login-action> ( -- action )\r
-    <action>\r
-        [ "login" login-template <html-content> ] >>display\r
-\r
-        [\r
-            {\r
-                { "username" [ v-required ] }\r
-                { "password" [ v-required ] }\r
-            } validate-params\r
-\r
-            "password" value\r
-            "username" value check-login\r
-            [ successful-login ] [ login-failed ] if*\r
-        ] >>submit ;\r
-\r
-! ! ! New user registration\r
-\r
-: user-exists ( -- * )\r
-    "username taken" validation-error\r
-    validation-failed ;\r
-\r
-: password-mismatch ( -- * )\r
-    "passwords do not match" validation-error\r
-    validation-failed ;\r
-\r
-: same-password-twice ( -- )\r
-    "new-password" value "verify-password" value =\r
-    [ password-mismatch ] unless ;\r
-\r
-: <register-action> ( -- action )\r
-    <page-action>\r
-        "register" login-template >>template\r
-\r
-        [\r
-            {\r
-                { "username" [ v-username ] }\r
-                { "realname" [ [ v-one-line ] v-optional ] }\r
-                { "new-password" [ v-password ] }\r
-                { "verify-password" [ v-password ] }\r
-                { "email" [ [ v-email ] v-optional ] }\r
-                { "captcha" [ v-captcha ] }\r
-            } validate-params\r
-\r
-            same-password-twice\r
-        ] >>validate\r
-\r
-        [\r
-            "username" value <user>\r
-                "realname" value >>realname\r
-                "new-password" value >>encoded-password\r
-                "email" value >>email\r
-                H{ } clone >>profile\r
-\r
-            users new-user [ user-exists ] unless*\r
-\r
-            login get init-user-profile\r
-\r
-            successful-login\r
-        ] >>submit ;\r
-\r
-! ! ! Editing user profile\r
-\r
-: <edit-profile-action> ( -- action )\r
-    <action>\r
-        [\r
-            logged-in-user get\r
-            [ username>> "username" set-value ]\r
-            [ realname>> "realname" set-value ]\r
-            [ email>> "email" set-value ]\r
-            tri\r
-        ] >>init\r
-\r
-        [ "edit-profile" login-template <html-content> ] >>display\r
-\r
-        [\r
-            uid "username" set-value\r
-\r
-            {\r
-                { "realname" [ [ v-one-line ] v-optional ] }\r
-                { "password" [ ] }\r
-                { "new-password" [ [ v-password ] v-optional ] }\r
-                { "verify-password" [ [ v-password ] v-optional ] } \r
-                { "email" [ [ v-email ] v-optional ] }\r
-            } validate-params\r
-\r
-            { "password" "new-password" "verify-password" }\r
-            [ value empty? not ] contains? [\r
-                "password" value uid check-login\r
-                [ "incorrect password" validation-error ] unless\r
-\r
-                same-password-twice\r
-            ] when\r
-        ] >>validate\r
-\r
-        [\r
-            logged-in-user get\r
-\r
-            "new-password" value dup empty?\r
-            [ drop ] [ >>encoded-password ] if\r
-\r
-            "realname" value >>realname\r
-            "email" value >>email\r
-\r
-            t >>changed?\r
-\r
-            drop\r
-\r
-            "$login" end-flow\r
-        ] >>submit ;\r
-\r
-! ! ! Password recovery\r
-\r
-SYMBOL: lost-password-from\r
-\r
-: current-host ( -- string )\r
-    request get host>> host-name or ;\r
-\r
-: new-password-url ( user -- url )\r
-    "new-password"\r
-    swap [\r
-        [ username>> "username" set ]\r
-        [ ticket>> "ticket" set ]\r
-        bi\r
-    ] H{ } make-assoc\r
-    derive-url ;\r
-\r
-: password-email ( user -- email )\r
-    smtp:<email>\r
-        [ "[ " % current-host % " ] password recovery" % ] "" make >>subject\r
-        lost-password-from get >>from\r
-        over email>> 1array >>to\r
-        [\r
-            "This e-mail was sent by the application server on " % current-host % "\n" %\r
-            "because somebody, maybe you, clicked on a ``recover password'' link in the\n" %\r
-            "login form, and requested a new password for the user named ``" %\r
-            over username>> % "''.\n" %\r
-            "\n" %\r
-            "If you believe that this request was legitimate, you may click the below link in\n" %\r
-            "your browser to set a new password for your account:\n" %\r
-            "\n" %\r
-            swap new-password-url %\r
-            "\n\n" %\r
-            "Love,\n" %\r
-            "\n" %\r
-            "  FactorBot\n" %\r
-        ] "" make >>body ;\r
-\r
-: send-password-email ( user -- )\r
-    '[ , password-email smtp:send-email ]\r
-    "E-mail send thread" spawn drop ;\r
-\r
-: <recover-action-1> ( -- action )\r
-    <action>\r
-        [ "recover-1" login-template <html-content> ] >>display\r
-\r
-        [\r
-            {\r
-                { "username" [ v-username ] }\r
-                { "email" [ v-email ] }\r
-                { "captcha" [ v-captcha ] }\r
-            } validate-params\r
-        ] >>validate\r
-\r
-        [\r
-            "email" value "username" value\r
-            users issue-ticket [\r
-                send-password-email\r
-            ] when*\r
-\r
-            "recover-2" login-template <html-content>\r
-        ] >>submit ;\r
-\r
-: <recover-action-3> ( -- action )\r
-    <action>\r
-        [\r
-            {\r
-                { "username" [ v-username ] }\r
-                { "ticket" [ v-required ] }\r
-            } validate-params\r
-        ] >>init\r
-\r
-        [ "recover-3" login-template <html-content> ] >>display\r
-\r
-        [\r
-            {\r
-                { "username" [ v-username ] }\r
-                { "ticket" [ v-required ] }\r
-                { "new-password" [ v-password ] }\r
-                { "verify-password" [ v-password ] }\r
-            } validate-params\r
-\r
-            same-password-twice\r
-        ] >>validate\r
-\r
-        [\r
-            "ticket" value\r
-            "username" value\r
-            users claim-ticket [\r
-                "new-password" value >>encoded-password\r
-                users update-user\r
-\r
-                "recover-4" login-template <html-content>\r
-            ] [\r
-                <400>\r
-            ] if*\r
-        ] >>submit ;\r
-\r
-! ! ! Logout\r
-: <logout-action> ( -- action )\r
-    <action>\r
-        [\r
-            f set-uid\r
-            "$login/login" end-flow\r
-        ] >>submit ;\r
-\r
-! ! ! Authentication logic\r
-\r
-TUPLE: protected < filter-responder capabilities ;\r
-\r
-C: <protected> protected\r
-\r
-: show-login-page ( -- response )\r
-    begin-flow\r
-    "$login/login" f <standard-redirect> ;\r
-\r
-: check-capabilities ( responder user -- ? )\r
-    [ capabilities>> ] bi@ subset? ;\r
-\r
-M: protected call-responder* ( path responder -- response )\r
-    uid dup [\r
-        users get-user 2dup check-capabilities [\r
-            [ logged-in-user set ] [ save-user-after ] bi\r
-            call-next-method\r
-        ] [\r
-            3drop show-login-page\r
-        ] if\r
-    ] [\r
-        3drop show-login-page\r
-    ] if ;\r
-\r
-M: login call-responder* ( path responder -- response )\r
-    dup login set\r
-    call-next-method ;\r
-\r
-: <login-boilerplate> ( responder -- responder' )\r
-    <boilerplate>\r
-        "boilerplate" login-template >>template ;\r
-\r
-: <login> ( responder -- auth )\r
-    login new-dispatcher\r
-        swap >>default\r
-        <login-action> <login-boilerplate> "login" add-responder\r
-        <logout-action> <login-boilerplate> "logout" add-responder\r
-        users-in-db >>users\r
-        sha-256 >>checksum ;\r
-\r
-! ! ! Configuration\r
-\r
-: allow-edit-profile ( login -- login )\r
-    <edit-profile-action> f <protected> <login-boilerplate>\r
-        "edit-profile" add-responder ;\r
-\r
-: allow-registration ( login -- login )\r
-    <register-action> <login-boilerplate>\r
-        "register" add-responder ;\r
-\r
-: allow-password-recovery ( login -- login )\r
-    <recover-action-1> <login-boilerplate>\r
-        "recover-password" add-responder\r
-    <recover-action-3> <login-boilerplate>\r
-        "new-password" add-responder ;\r
-\r
-: allow-edit-profile? ( -- ? )\r
-    login get responders>> "edit-profile" swap key? ;\r
-\r
-: allow-registration? ( -- ? )\r
-    login get responders>> "register" swap key? ;\r
-\r
-: allow-password-recovery? ( -- ? )\r
-    login get responders>> "recover-password" swap key? ;\r
diff --git a/extra/http/server/auth/login/login.xml b/extra/http/server/auth/login/login.xml
deleted file mode 100644 (file)
index 545d7e0..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-       <t:title>Login</t:title>
-
-       <t:form t:action="login">
-
-               <table>
-
-                       <tr>
-                               <th class="field-label">User name:</th>
-                               <td><t:field t:name="username" /></td>
-                       </tr>
-
-                       <tr>
-                               <th class="field-label">Password:</th>
-                               <td><t:password t:name="password" /></td>
-                       </tr>
-
-               </table>
-
-               <p>
-
-                       <input type="submit" value="Log in" />
-                       <t:validation-messages />
-
-               </p>
-
-       </t:form>
-
-       <p>
-               <t:if code="http.server.auth.login:login-failed?">
-                       <t:a t:href="register">Register</t:a>
-               </t:if>
-               |
-               <t:if code="http.server.auth.login:allow-password-recovery?">
-                       <t:a t:href="recover-password">Recover Password</t:a>
-               </t:if>
-       </p>
-
-</t:chloe>
diff --git a/extra/http/server/auth/login/recover-1.xml b/extra/http/server/auth/login/recover-1.xml
deleted file mode 100644 (file)
index 21fbe6f..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-       <t:title>Recover lost password: step 1 of 4</t:title>
-
-       <p>Enter the username and e-mail address you used to register for this site, and you will receive a link for activating a new password.</p>
-
-       <t:form t:action="recover-password">
-
-               <table>
-
-                       <tr>
-                               <th class="field-label">User name:</th>
-                               <td><t:field t:name="username" /></td>
-                       </tr>
-
-                       <tr>
-                               <th class="field-label">E-mail:</th>
-                               <td><t:field t:name="email" /></td>
-                       </tr>
-
-                       <tr>
-                               <th class="field-label">Captcha:</th>
-                               <td><t:field t:name="captcha" /></td>
-                       </tr>
-
-                       <tr>
-                               <td></td>
-                               <td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to e-mail you will be blocked.</td>
-                       </tr>
-
-               </table>
-
-               <input type="submit" value="Recover password" />
-
-       </t:form>
-
-</t:chloe>
diff --git a/extra/http/server/auth/login/recover-2.xml b/extra/http/server/auth/login/recover-2.xml
deleted file mode 100644 (file)
index c7819bd..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-       <t:title>Recover lost password: step 2 of 4</t:title>
-
-       <p>If you entered the correct username and e-mail address, you should receive an e-mail shortly. Click the link in the e-mail for further instructions.</p>
-
-</t:chloe>
diff --git a/extra/http/server/auth/login/recover-3.xml b/extra/http/server/auth/login/recover-3.xml
deleted file mode 100644 (file)
index 2e412d1..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-       <t:title>Recover lost password: step 3 of 4</t:title>
-
-       <p>Choose a new password for your account.</p>
-
-       <t:form t:action="new-password">
-
-               <table>
-
-                       <t:hidden t:name="username" />
-                       <t:hidden t:name="ticket" />
-
-                       <tr>
-                               <th class="field-label">Password:</th>
-                               <td><t:password t:name="new-password" /></td>
-                       </tr>
-
-                       <tr>
-                               <th class="field-label">Verify password:</th>
-                               <td><t:password t:name="verify-password" /></td>
-                       </tr>
-
-                       <tr>
-                               <td></td>
-                               <td>Enter your password twice to ensure it is correct.</td>
-                       </tr>
-
-               </table>
-
-               <p>
-                       <input type="submit" value="Set password" />
-                       <t:validation-messages />
-               </p>
-
-       </t:form>
-
-</t:chloe>
diff --git a/extra/http/server/auth/login/recover-4.xml b/extra/http/server/auth/login/recover-4.xml
deleted file mode 100755 (executable)
index f5d02fa..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version='1.0' ?>\r
-\r
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">\r
-\r
-       <t:title>Recover lost password: step 4 of 4</t:title>\r
-\r
-       <p>Your password has been reset. You may now <t:a t:href="login">log in</t:a>.</p>\r
-\r
-</t:chloe>\r
diff --git a/extra/http/server/auth/login/register.xml b/extra/http/server/auth/login/register.xml
deleted file mode 100644 (file)
index 9815f21..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-       <t:title>New User Registration</t:title>
-
-       <t:form t:action="register">
-
-               <table>
-
-                       <tr>
-                               <th class="field-label">User name:</th>
-                               <td><t:field t:name="username" /></td>
-                       </tr>
-
-                       <tr>
-                               <th class="field-label">Real name:</th>
-                               <td><t:field t:name="realname" /></td>
-                       </tr>
-
-                       <tr>
-                               <td></td>
-                               <td>Specifying a real name is optional.</td>
-                       </tr>
-
-                       <tr>
-                               <th class="field-label">Password:</th>
-                               <td><t:password t:name="new-password" /></td>
-                       </tr>
-
-                       <tr>
-                               <th class="field-label">Verify:</th>
-                               <td><t:password t:name="verify-password" /></td>
-                       </tr>
-
-                       <tr>
-                               <td></td>
-                               <td>Enter your password twice to ensure it is correct.</td>
-                       </tr>
-
-                       <tr>
-                               <th class="field-label">E-mail:</th>
-                               <td><t:field t:name="email" /></td>
-                       </tr>
-
-                       <tr>
-                               <td></td>
-                               <td>Specifying an e-mail address is optional. It enables the "recover password" feature.</td>
-                       </tr>
-
-                       <tr>
-                               <th class="field-label">Captcha:</th>
-                               <td><t:field t:name="captcha" /></td>
-                       </tr>
-
-                       <tr>
-                               <td></td>
-                               <td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to register will be blocked.</td>
-                       </tr>
-
-               </table>
-
-               <p>
-
-                       <input type="submit" value="Register" />
-                       <t:validation-messages />
-
-               </p>
-
-       </t:form>
-
-</t:chloe>
diff --git a/extra/http/server/auth/providers/assoc/assoc-tests.factor b/extra/http/server/auth/providers/assoc/assoc-tests.factor
deleted file mode 100755 (executable)
index 91e802b..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-IN: http.server.auth.providers.assoc.tests\r
-USING: http.server.actions http.server.auth.providers \r
-http.server.auth.providers.assoc http.server.auth.login\r
-tools.test namespaces accessors kernel ;\r
-\r
-<action> <login>\r
-    <users-in-memory> >>users\r
-login set\r
-\r
-[ t ] [\r
-    "slava" <user>\r
-        "foobar" >>encoded-password\r
-        "slava@factorcode.org" >>email\r
-        H{ } clone >>profile\r
-    users new-user\r
-    username>> "slava" =\r
-] unit-test\r
-\r
-[ f ] [\r
-    "slava" <user>\r
-        H{ } clone >>profile\r
-    users new-user\r
-] unit-test\r
-\r
-[ f ] [ "fdasf" "slava" check-login >boolean ] unit-test\r
-\r
-[ ] [ "foobar" "slava" check-login "user" set ] unit-test\r
-\r
-[ t ] [ "user" get >boolean ] unit-test\r
-\r
-[ ] [ "user" get "fdasf" >>encoded-password drop ] unit-test\r
-\r
-[ t ] [ "fdasf" "slava" check-login >boolean ] unit-test\r
-\r
-[ f ] [ "foobar" "slava" check-login >boolean ] unit-test\r
diff --git a/extra/http/server/auth/providers/assoc/assoc.factor b/extra/http/server/auth/providers/assoc/assoc.factor
deleted file mode 100755 (executable)
index d6ba587..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-! Copyright (C) 2008 Slava Pestov.\r
-! See http://factorcode.org/license.txt for BSD license.\r
-IN: http.server.auth.providers.assoc\r
-USING: accessors assocs kernel\r
-http.server.auth.providers ;\r
-\r
-TUPLE: users-in-memory assoc ;\r
-\r
-: <users-in-memory> ( -- provider )\r
-    H{ } clone users-in-memory boa ;\r
-\r
-M: users-in-memory get-user ( username provider -- user/f )\r
-    assoc>> at ;\r
-\r
-M: users-in-memory update-user ( user provider -- ) 2drop ;\r
-\r
-M: users-in-memory new-user ( user provider -- user/f )\r
-    [ dup username>> ] dip assoc>>\r
-    2dup key? [ 3drop f ] [ pick [ set-at ] dip ] if ;\r
diff --git a/extra/http/server/auth/providers/db/db-tests.factor b/extra/http/server/auth/providers/db/db-tests.factor
deleted file mode 100755 (executable)
index a6a9235..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-IN: http.server.auth.providers.db.tests\r
-USING: http.server.actions\r
-http.server.auth.login\r
-http.server.auth.providers\r
-http.server.auth.providers.db tools.test\r
-namespaces db db.sqlite db.tuples continuations\r
-io.files accessors kernel ;\r
-\r
-<action> <login>\r
-    users-in-db >>users\r
-login set\r
-\r
-[ "auth-test.db" temp-file delete-file ] ignore-errors\r
-\r
-"auth-test.db" temp-file sqlite-db [\r
-\r
-    init-users-table\r
-\r
-    [ t ] [\r
-        "slava" <user>\r
-            "foobar" >>encoded-password\r
-            "slava@factorcode.org" >>email\r
-            H{ } clone >>profile\r
-            users new-user\r
-            username>> "slava" =\r
-    ] unit-test\r
-\r
-    [ f ] [\r
-        "slava" <user>\r
-            H{ } clone >>profile\r
-        users new-user\r
-    ] unit-test\r
-\r
-    [ f ] [ "fdasf" "slava" check-login >boolean ] unit-test\r
-\r
-    [ ] [ "foobar" "slava" check-login "user" set ] unit-test\r
-\r
-    [ t ] [ "user" get >boolean ] unit-test\r
-\r
-    [ ] [ "user" get "fdasf" >>encoded-password drop ] unit-test\r
-\r
-    [ ] [ "user" get users update-user ] unit-test\r
-\r
-    [ t ] [ "fdasf" "slava" check-login >boolean ] unit-test\r
-\r
-    [ f ] [ "foobar" "slava" check-login >boolean ] unit-test\r
-] with-db\r
diff --git a/extra/http/server/auth/providers/db/db.factor b/extra/http/server/auth/providers/db/db.factor
deleted file mode 100755 (executable)
index 3ed4845..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-! Copyright (C) 2008 Slava Pestov.
-! See http://factorcode.org/license.txt for BSD license.
-USING: db db.tuples db.types accessors
-http.server.auth.providers kernel continuations
-classes.singleton ;
-IN: http.server.auth.providers.db
-
-user "USERS"
-{
-    { "username" "USERNAME" { VARCHAR 256 } +user-assigned-id+ }
-    { "realname" "REALNAME" { VARCHAR 256 } }
-    { "password" "PASSWORD" BLOB +not-null+ }
-    { "salt" "SALT" INTEGER +not-null+ }
-    { "email" "EMAIL" { VARCHAR 256 } }
-    { "ticket" "TICKET" { VARCHAR 256 } }
-    { "capabilities" "CAPABILITIES" FACTOR-BLOB }
-    { "profile" "PROFILE" FACTOR-BLOB }
-    { "deleted" "DELETED" INTEGER +not-null+ }
-} define-persistent
-
-: init-users-table user ensure-table ;
-
-SINGLETON: users-in-db
-
-M: users-in-db get-user
-    drop <user> select-tuple ;
-
-M: users-in-db new-user
-    drop
-    [
-        user new
-            over username>> >>username
-        select-tuple [
-            drop f
-        ] [
-            dup insert-tuple
-        ] if
-    ] with-transaction ;
-
-M: users-in-db update-user
-    drop update-tuple ;
diff --git a/extra/http/server/auth/providers/null/null.factor b/extra/http/server/auth/providers/null/null.factor
deleted file mode 100755 (executable)
index 30f6dbd..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-! Copyright (C) 2008 Slava Pestov.\r
-! See http://factorcode.org/license.txt for BSD license.\r
-USING: http.server.auth.providers kernel ;\r
-IN: http.server.auth.providers.null\r
-\r
-TUPLE: no-users ;\r
-\r
-: no-users T{ no-users } ;\r
-\r
-M: no-users get-user 2drop f ;\r
-\r
-M: no-users new-user 2drop f ;\r
-\r
-M: no-users update-user 2drop ;\r
diff --git a/extra/http/server/auth/providers/providers.factor b/extra/http/server/auth/providers/providers.factor
deleted file mode 100755 (executable)
index a51c4da..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-! Copyright (C) 2008 Slava Pestov.\r
-! See http://factorcode.org/license.txt for BSD license.\r
-USING: kernel accessors random math.parser locals\r
-sequences math ;\r
-IN: http.server.auth.providers\r
-\r
-TUPLE: user\r
-username realname\r
-password salt\r
-email ticket capabilities profile deleted changed? ;\r
-\r
-: <user> ( username -- user )\r
-    user new\r
-        swap >>username\r
-        0 >>deleted ;\r
-\r
-GENERIC: get-user ( username provider -- user/f )\r
-\r
-GENERIC: update-user ( user provider -- )\r
-\r
-GENERIC: new-user ( user provider -- user/f )\r
-\r
-! Password recovery support\r
-\r
-:: issue-ticket ( email username provider -- user/f )\r
-    [let | user [ username provider get-user ] |\r
-        user [\r
-            user email>> length 0 > [\r
-                user email>> email = [\r
-                    user\r
-                    256 random-bits >hex >>ticket\r
-                    dup provider update-user\r
-                ] [ f ] if\r
-            ] [ f ] if\r
-        ] [ f ] if\r
-    ] ;\r
-\r
-:: claim-ticket ( ticket username provider -- user/f )\r
-    [let | user [ username provider get-user ] |\r
-        user [\r
-            user ticket>> ticket = [\r
-                user f >>ticket dup provider update-user\r
-            ] [ f ] if\r
-        ] [ f ] if\r
-    ] ;\r
-\r
-! For configuration\r
-\r
-: add-user ( provider user -- provider )\r
-    over new-user [ "User exists" throw ] when ;\r
diff --git a/extra/http/server/boilerplate/boilerplate.factor b/extra/http/server/boilerplate/boilerplate.factor
deleted file mode 100644 (file)
index 96c59ed..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-! Copyright (c) 2008 Slava Pestov
-! See http://factorcode.org/license.txt for BSD license.
-USING: accessors kernel namespaces http.server html.templates
-locals ;
-IN: http.server.boilerplate
-
-TUPLE: boilerplate < filter-responder template ;
-
-: <boilerplate> f boilerplate boa ;
-
-M:: boilerplate call-responder* ( path responder -- )
-    path responder call-next-method
-    dup content-type>> "text/html" = [
-        clone [| body |
-            [ body responder template>> with-boilerplate ]
-        ] change-body
-    ] when ;
diff --git a/extra/http/server/callbacks/callbacks-tests.factor b/extra/http/server/callbacks/callbacks-tests.factor
deleted file mode 100755 (executable)
index 31ea164..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-IN: http.server.callbacks\r
-USING: http.server.actions http.server.callbacks accessors\r
-http.server http tools.test namespaces io fry sequences\r
-splitting kernel hashtables continuations ;\r
-\r
-[ 123 ] [\r
-    [\r
-        init-request\r
-\r
-        <request> "GET" >>method request set\r
-        [\r
-            exit-continuation set\r
-            { }\r
-            <action> [ [ "hello" print 123 ] show-final ] >>display\r
-            <callback-responder>\r
-            call-responder\r
-        ] callcc1\r
-    ] with-scope\r
-] unit-test\r
-\r
-[\r
-    init-request\r
-\r
-    <action> [\r
-        [\r
-            "hello" print\r
-            '[ , write ] <html-content>\r
-        ] show-page\r
-        "byebye" print\r
-        [ 123 ] show-final\r
-    ] >>display\r
-    <callback-responder> "r" set\r
-\r
-    [ 123 ] [\r
-        [\r
-            exit-continuation set\r
-            <request> "GET" >>method request set\r
-            { } "r" get call-responder\r
-        ] callcc1\r
-\r
-        body>> first\r
-\r
-        <request>\r
-            "GET" >>method\r
-            swap cont-id associate >>query\r
-            "/" >>path\r
-        request set\r
-\r
-        [\r
-            exit-continuation set\r
-            { }\r
-            "r" get call-responder\r
-        ] callcc1\r
-\r
-        ! get-post-get\r
-        <request>\r
-            "GET" >>method\r
-            swap "location" header "=" last-split1 nip cont-id associate >>query\r
-            "/" >>path\r
-        request set\r
-\r
-        [\r
-            exit-continuation set\r
-            { }\r
-            "r" get call-responder\r
-        ] callcc1\r
-    ] unit-test\r
-] with-scope\r
diff --git a/extra/http/server/callbacks/callbacks.factor b/extra/http/server/callbacks/callbacks.factor
deleted file mode 100755 (executable)
index 3b819e0..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-! Copyright (C) 2004 Chris Double.\r
-! Copyright (C) 2006, 2008 Slava Pestov.\r
-! See http://factorcode.org/license.txt for BSD license.\r
-USING: http http.server io kernel math namespaces\r
-continuations calendar sequences assocs hashtables\r
-accessors arrays alarms quotations combinators fry assocs.lib ;\r
-IN: http.server.callbacks\r
-\r
-SYMBOL: responder\r
-\r
-TUPLE: callback-responder responder callbacks ;\r
-\r
-: <callback-responder> ( responder -- responder' )\r
-    #! A continuation responder is a special type of session\r
-    #! manager. However it works entirely differently from\r
-    #! the URL and cookie session managers.\r
-    H{ } clone callback-responder boa ;\r
-\r
-TUPLE: callback cont quot expires alarm responder ;\r
-\r
-: timeout 20 minutes ;\r
-\r
-: timeout-callback ( callback -- )\r
-    [ alarm>> cancel-alarm ]\r
-    [ dup responder>> callbacks>> delete-at ]\r
-    bi ;\r
-\r
-: touch-callback ( callback -- )\r
-    dup expires>> [\r
-        dup alarm>> [ cancel-alarm ] when*\r
-        dup '[ , timeout-callback ] timeout later >>alarm\r
-    ] when drop ;\r
-\r
-: <callback> ( cont quot expires? -- callback )\r
-    f callback-responder get callback boa\r
-    dup touch-callback ;\r
-\r
-: invoke-callback ( callback -- response )\r
-    [ touch-callback ]\r
-    [ quot>> request get exit-continuation get 3array ]\r
-    [ cont>> continue-with ]\r
-    tri ;\r
-\r
-: register-callback ( cont quot expires? -- id )\r
-    <callback> callback-responder get callbacks>> set-at-unique ;\r
-\r
-: forward-to-url ( url query -- * )\r
-    #! When executed inside a 'show' call, this will force a\r
-    #! HTTP 302 to occur to instruct the browser to forward to\r
-    #! the request URL.\r
-    <temporary-redirect> exit-with ;\r
-\r
-: cont-id "factorcontid" ;\r
-\r
-: forward-to-id ( id -- * )\r
-    #! When executed inside a 'show' call, this will force a\r
-    #! HTTP 302 to occur to instruct the browser to forward to\r
-    #! the request URL.\r
-    f swap cont-id associate forward-to-url ;\r
-\r
-: restore-request ( pair -- )\r
-    first3 exit-continuation set request set call ;\r
-\r
-SYMBOL: post-refresh-get?\r
-\r
-: redirect-to-here ( -- )\r
-    #! Force a redirect to the client browser so that the browser\r
-    #! goes to the current point in the code. This forces an URL\r
-    #! change on the browser so that refreshing that URL will\r
-    #! immediately run from this code point. This prevents the\r
-    #! "this request will issue a POST" warning from the browser\r
-    #! and prevents re-running the previous POST logic. This is\r
-    #! known as the 'post-refresh-get' pattern.\r
-    post-refresh-get? get [\r
-        [\r
-            [ ] t register-callback forward-to-id\r
-        ] callcc1 restore-request\r
-    ] [\r
-        post-refresh-get? on\r
-    ] if ;\r
-\r
-SYMBOL: current-show\r
-\r
-: store-current-show ( -- )\r
-    #! Store the current continuation in the variable 'current-show'\r
-    #! so it can be returned to later by 'quot-id'. Note that it\r
-    #! recalls itself when the continuation is called to ensure that\r
-    #! it resets its value back to the most recent show call.\r
-    [ current-show set f ] callcc1\r
-    [ restore-request store-current-show ] when* ;\r
-\r
-: show-final ( quot -- * )\r
-    [ redirect-to-here store-current-show ] dip\r
-    call exit-with ; inline\r
-\r
-: resuming-callback ( responder request -- id )\r
-    cont-id query-param swap callbacks>> at ;\r
-\r
-M: callback-responder call-responder* ( path responder -- response )\r
-    '[\r
-        , ,\r
-\r
-        [ callback-responder set ]\r
-        [ request get resuming-callback ] bi\r
-\r
-        [\r
-            invoke-callback\r
-        ] [\r
-            callback-responder get responder>> call-responder\r
-        ] ?if\r
-    ] with-exit-continuation ;\r
-\r
-: show-page ( quot -- )\r
-    [ redirect-to-here store-current-show ] dip\r
-    [\r
-        [ ] t register-callback swap call exit-with\r
-    ] callcc1 restore-request ; inline\r
-\r
-: quot-id ( quot -- id )\r
-    current-show get swap t register-callback ;\r
-\r
-: quot-url ( quot -- url )\r
-    quot-id f swap cont-id associate derive-url ;\r
index 20eb7318d0d6fc9d6c230418dee028a36d025378..a706ee69988e571ea310c5a6e3b2ea482f7048e3 100755 (executable)
@@ -14,13 +14,12 @@ IN: http.server.cgi
         "HTTP/" request get version>> append "SERVER_PROTOCOL" set\r
         "Factor" "SERVER_SOFTWARE" set\r
 \r
-        dup "PATH_TRANSLATED" set\r
-        "SCRIPT_FILENAME" set\r
+        [ "PATH_TRANSLATED" set ] [ "SCRIPT_FILENAME" set ] bi\r
 \r
-        request get path>> "SCRIPT_NAME" set\r
+        request get url>> path>> "SCRIPT_NAME" set\r
 \r
-        request get host>> "SERVER_NAME" set\r
-        request get port>> number>string "SERVER_PORT" set\r
+        request get url>> host>> "SERVER_NAME" set\r
+        request get url>> port>> number>string "SERVER_PORT" set\r
         "" "PATH_INFO" set\r
         "" "REMOTE_HOST" set\r
         "" "REMOTE_ADDR" set\r
diff --git a/extra/http/server/db/db-tests.factor b/extra/http/server/db/db-tests.factor
deleted file mode 100644 (file)
index 0c34745..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-IN: http.server.db.tests
-USING: tools.test http.server.db ;
-
-\ <db-persistence> must-infer
diff --git a/extra/http/server/db/db.factor b/extra/http/server/db/db.factor
deleted file mode 100755 (executable)
index 73d4c35..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-! Copyright (C) 2008 Slava Pestov.\r
-! See http://factorcode.org/license.txt for BSD license.\r
-USING: db db.pools io.pools http.server http.server.sessions\r
-kernel accessors continuations namespaces destructors ;\r
-IN: http.server.db\r
-\r
-TUPLE: db-persistence < filter-responder pool ;\r
-\r
-: <db-persistence> ( responder params db -- responder' )\r
-    <db-pool> db-persistence boa ;\r
-\r
-M: db-persistence call-responder*\r
-    [\r
-        pool>> [ acquire-connection ] keep\r
-        [ return-connection-later ] [ drop db set ] 2bi\r
-    ]\r
-    [ call-next-method ] bi ;\r
diff --git a/extra/http/server/flows/flows.factor b/extra/http/server/flows/flows.factor
deleted file mode 100644 (file)
index 7a9b362..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-! Copyright (C) 2008 Slava Pestov.
-! See http://factorcode.org/license.txt for BSD license.
-USING: accessors namespaces sequences arrays kernel
-assocs assocs.lib hashtables math.parser
-html.elements http http.server http.server.sessions ;
-IN: http.server.flows
-
-TUPLE: flows < filter-responder ;
-
-C: <flows> flows
-
-: begin-flow* ( -- id )
-    request get
-    [ path>> ] [ request-params ] [ method>> ] tri 3array
-    flows sget set-at-unique
-    session-changed ;
-
-: end-flow-post ( path params -- response )
-    request [
-        clone
-            "POST" >>method
-            swap >>post-data
-            swap >>path
-    ] change
-    request get path>> split-path
-    flows get responder>> call-responder ;
-
-: end-flow* ( default id -- response )
-    flows sget at
-    [ first3 "POST" = [ end-flow-post ] [ <standard-redirect> ] if ]
-    [ f <standard-redirect> ] ?if ;
-
-SYMBOL: flow-id
-
-: flow-id-key "factorflowid" ;
-
-: begin-flow ( -- )
-    begin-flow* flow-id set ;
-
-: end-flow ( default -- response )
-    flow-id get end-flow* ;
-
-: add-flow-id ( query -- query' )
-    flow-id get [ flow-id-key associate assoc-union ] when* ;
-
-: flow-form-field ( -- )
-    flow-id get [
-        <input
-            "hidden" =type
-            flow-id-key =name
-            =value
-        input/>
-    ] when* ;
-
-M: flows call-responder*
-    dup flows set
-    [ add-flow-id ] add-link-hook
-    [ flow-form-field ] add-form-hook
-    flow-id-key request get request-params at flow-id set
-    call-next-method ;
-
-M: flows init-session*
-    H{ } clone flows sset
-    call-next-method ;
index 0aed425adecd321d4744026baa9c48ec96e2dff8..fb1abcc6e03c56e4debc91cadd33a14b8dc34dce 100755 (executable)
@@ -1,27 +1,52 @@
 USING: http.server tools.test kernel namespaces accessors
-io http math sequences assocs arrays classes words ;
+io http math sequences assocs arrays classes words urls ;
 IN: http.server.tests
 
 \ find-responder must-infer
 
 [
     <request>
-    http >>protocol
-    "www.apple.com" >>host
-    "/xxx/bar" >>path
-    { { "a" "b" } } >>query
+        <url>
+            "http" >>protocol
+            "www.apple.com" >>host
+            "/xxx/bar" >>path
+            { { "a" "b" } } >>query
+        >>url
     request set
 
     [ ] link-hook set
 
-    [ "http://www.apple.com:80/xxx/bar?a=b" ] [ f f derive-url ] unit-test
-    [ "http://www.apple.com:80/xxx/baz?a=b" ] [ "baz" f derive-url ] unit-test
-    [ "http://www.apple.com:80/xxx/baz?c=d" ] [ "baz" { { "c" "d" } } derive-url ] unit-test
-    [ "http://www.apple.com:80/xxx/bar?c=d" ] [ f { { "c" "d" } } derive-url ] unit-test
-    [ "http://www.apple.com:80/flip?a=b" ] [ "/flip" f derive-url ] unit-test
-    [ "http://www.apple.com:80/flip?c=d" ] [ "/flip" { { "c" "d" } } derive-url ] unit-test
-    [ "http://www.jedit.org" ] [ "http://www.jedit.org" f derive-url ] unit-test
-    [ "http://www.jedit.org?a=b" ] [ "http://www.jedit.org" { { "a" "b" } } derive-url ] unit-test
+    [ "http://www.apple.com:80/xxx/bar" ] [ 
+        <url> adjust-url url>string 
+    ] unit-test
+
+    [ "http://www.apple.com:80/xxx/baz" ] [
+        <url> "baz" >>path adjust-url url>string
+    ] unit-test
+    
+    [ "http://www.apple.com:80/xxx/baz?c=d" ] [
+        <url> "baz" >>path { { "c" "d" } } >>query adjust-url url>string
+    ] unit-test
+    
+    [ "http://www.apple.com:80/xxx/bar?c=d" ] [
+        <url> { { "c" "d" } } >>query adjust-url url>string
+    ] unit-test
+    
+    [ "http://www.apple.com:80/flip" ] [
+        <url> "/flip" >>path adjust-url url>string
+    ] unit-test
+    
+    [ "http://www.apple.com:80/flip?c=d" ] [
+        <url> "/flip" >>path { { "c" "d" } } >>query adjust-url url>string
+    ] unit-test
+    
+    [ "http://www.jedit.org:80/" ] [
+        "http://www.jedit.org" >url adjust-url url>string
+    ] unit-test
+    
+    [ "http://www.jedit.org:80/?a=b" ] [
+        "http://www.jedit.org" >url { { "a" "b" } } >>query adjust-url url>string
+    ] unit-test
 ] with-scope
 
 TUPLE: mock-responder path ;
@@ -31,7 +56,7 @@ C: <mock-responder> mock-responder
 M: mock-responder call-responder*
     nip
     path>> on
-    [ ] <text-content> ;
+    [ ] "text/plain" <content> ;
 
 : check-dispatch ( tag path -- ? )
     H{ } clone base-paths set
@@ -84,7 +109,7 @@ C: <path-check-responder> path-check-responder
 
 M: path-check-responder call-responder*
     drop
-    >array <text-content> ;
+    >array "text/plain" <content> ;
 
 [ { "c" } ] [
     H{ } clone base-paths set
@@ -125,7 +150,7 @@ C: <base-path-check-responder> base-path-check-responder
 M: base-path-check-responder call-responder*
     2drop
     "$funny-dispatcher" resolve-base-path
-    <text-content> ;
+    "text/plain" <content> ;
 
 [ ] [
     <dispatcher>
index d68c66b829643629a4060e740c668e424069818d..2fd706432bce2c95a5370c3037bc40b7cf0c697d 100755 (executable)
@@ -2,23 +2,16 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: assocs kernel namespaces io io.timeouts strings splitting
 threads sequences prettyprint io.server logging calendar http
-html.streams html.elements accessors math.parser
-combinators.lib tools.vocabs debugger continuations random
-combinators destructors io.encodings.8-bit fry classes words
-math rss json.writer ;
+html.streams html.components html.elements html.templates
+accessors math.parser combinators.lib tools.vocabs debugger
+continuations random combinators destructors io.streams.string
+io.encodings.8-bit fry classes words math urls
+arrays vocabs.loader ;
 IN: http.server
 
 ! path is a sequence of path component strings
-
 GENERIC: call-responder* ( path responder -- response )
 
-: request-params ( request -- assoc )
-    dup method>> {
-        { "GET" [ query>> ] }
-        { "HEAD" [ query>> ] }
-        { "POST" [ post-data>> ] }
-    } case ;
-
 : <content> ( body content-type -- response )
     <response>
         200 >>code
@@ -26,21 +19,6 @@ GENERIC: call-responder* ( path responder -- response )
         swap >>content-type
         swap >>body ;
 
-: <text-content> ( body -- response )
-    "text/plain" <content> ;
-
-: <html-content> ( body -- response )
-    "text/html" <content> ;
-
-: <xml-content> ( body -- response )
-    "text/xml" <content> ;
-
-: <feed-content> ( feed -- response )
-    '[ , feed>xml ] "text/xml" <content> ;
-
-: <json-content> ( obj -- response )
-    '[ , >json ] "application/json" <content> ;
-
 TUPLE: trivial-responder response ;
 
 C: <trivial-responder> trivial-responder
@@ -55,7 +33,8 @@ M: trivial-responder call-responder* nip response>> call ;
     </html> ;
 
 : <trivial-response> ( code message -- response )
-    2dup '[ , , trivial-response-body ] <html-content>
+    2dup [ trivial-response-body ] with-string-writer
+    "text/html" <content>
         swap >>message
         swap >>code ;
 
@@ -69,7 +48,7 @@ SYMBOL: 404-responder
 
 [ <404> ] <trivial-responder> 404-responder set-global
 
-SYMBOL: base-paths
+SYMBOL: responder-nesting
 
 : invert-slice ( slice -- slice' )
     dup slice? [
@@ -78,86 +57,81 @@ SYMBOL: base-paths
         drop { }
     ] if ;
 
-: add-base-path ( path dispatcher -- )
-    [ invert-slice ] [ class word-name ] bi*
-    base-paths get set-at ;
+: vocab-path ( vocab -- path )
+    dup vocab-dir vocab-append-path ;
+
+: vocab-path-of ( dispatcher -- path )
+    class word-vocabulary vocab-path ;
+
+: add-responder-path ( path dispatcher -- )
+    [ [ invert-slice ] [ [ vocab-path-of ] keep ] bi* 3array ]
+    [ nip class word-name ] 2bi
+    responder-nesting get set-at ;
 
 : call-responder ( path responder -- response )
-    [ add-base-path ] [ call-responder* ] 2bi ;
+    [ add-responder-path ] [ call-responder* ] 2bi ;
 
-SYMBOL: link-hook
+: nested-responders ( -- seq )
+    responder-nesting get assocs:values [ third ] map ;
 
-: add-link-hook ( quot -- )
-    link-hook [ compose ] change ; inline
+: each-responder ( quot -- )
+   nested-responders swap each ; inline
 
-: modify-query ( query -- query )
-    link-hook get call ;
+: responder-path ( string -- pair )
+    dup responder-nesting get at
+    [ ] [ "No such responder: " swap append throw ] ?if ;
 
 : base-path ( string -- path )
-    dup base-paths get at
-    [ ] [ "No such responder: " swap append throw ] ?if ;
+    responder-path first ;
 
-: resolve-base-path ( string -- string' )
-    "$" ?head [
+: template-path ( string -- path )
+    responder-path second ;
+
+: resolve-responder-path ( string quot -- string' )
+    [ "$" ?head ] dip '[
         [
-            "/" split1 [ base-path [ "/" % % ] each "/" % ] dip %
+            "/" split1 [ @ [ "/" % % ] each "/" % ] dip %
         ] "" make
-    ] when ;
-
-: link>string ( url query -- url' )
-    [ resolve-base-path ] [ modify-query ] bi* (link>string) ;
-
-: write-link ( url query -- )
-    link>string write ;
+    ] when ; inline
 
-SYMBOL: form-hook
-
-: add-form-hook ( quot -- )
-    form-hook [ compose ] change ;
-
-: hidden-form-field ( -- )
-    form-hook get call ;
+: resolve-base-path ( string -- string' )
+    [ base-path ] resolve-responder-path ;
 
-: absolute-redirect ( to query -- url )
-    #! Same host.
-    request get clone
-    swap [ >>query ] when*
-    swap url-encode >>path
-    [ modify-query ] change-query
-    request-url ;
+: resolve-template-path ( string -- string' )
+    [ template-path ] resolve-responder-path ;
 
-: replace-last-component ( path with -- path' )
-    [ "/" last-split1 drop "/" ] dip 3append ;
+GENERIC: modify-query ( query responder -- query' )
 
-: relative-redirect ( to query -- url )
-    request get clone
-    swap [ >>query ] when*
-    swap [ '[ , replace-last-component ] change-path ] when*
-    [ modify-query ] change-query
-    request-url ;
+M: object modify-query drop ;
 
-: derive-url ( to query -- url )
-    {
-        { [ over "http://" head? ] [ link>string ] }
-        { [ over "/" head? ] [ absolute-redirect ] }
-        { [ over "$" head? ] [ [ resolve-base-path ] dip derive-url ] }
-        [ relative-redirect ]
-    } cond ;
+: adjust-url ( url -- url' )
+    clone
+        [ dup [ modify-query ] each-responder ] change-query
+        [ resolve-base-path ] change-path
+    request get url>>
+        clone
+        f >>query
+    swap derive-url ensure-port ;
 
-: <redirect> ( to query code message -- response )
-    <trivial-response> -rot derive-url "location" set-header ;
+: <custom-redirect> ( url code message -- response )
+    <trivial-response>
+        swap dup url? [ adjust-url ] when
+        "location" set-header ;
 
-\ <redirect> DEBUG add-input-logging
+\ <custom-redirect> DEBUG add-input-logging
 
 : <permanent-redirect> ( to query -- response )
-    301 "Moved Permanently" <redirect> ;
+    301 "Moved Permanently" <custom-redirect> ;
 
 : <temporary-redirect> ( to query -- response )
-    307 "Temporary Redirect" <redirect> ;
+    307 "Temporary Redirect" <custom-redirect> ;
 
-: <standard-redirect> ( to query -- response )
-    request get method>> "POST" =
-    [ <permanent-redirect> ] [ <temporary-redirect> ] if ;
+: <redirect> ( to query -- response )
+    request get method>> {
+        { "GET" [ <temporary-redirect> ] }
+        { "HEAD" [ <temporary-redirect> ] }
+        { "POST" [ <permanent-redirect> ] }
+    } case ;
 
 TUPLE: dispatcher default responders ;
 
@@ -187,7 +161,7 @@ TUPLE: vhost-dispatcher default responders ;
     404-responder get H{ } clone vhost-dispatcher boa ;
 
 : find-vhost ( dispatcher -- responder )
-    request get host>> over responders>> at*
+    request get url>> host>> over responders>> at*
     [ nip ] [ drop default>> ] if ;
 
 M: vhost-dispatcher call-responder* ( path dispatcher -- response )
@@ -242,35 +216,28 @@ SYMBOL: development-mode
 LOG: httpd-hit NOTICE
 
 : log-request ( request -- )
-    { method>> host>> path>> } map-exec-with httpd-hit ;
-
-SYMBOL: exit-continuation
-
-: exit-with exit-continuation get continue-with ;
-
-: with-exit-continuation ( quot -- )
-    '[ exit-continuation set @ ] callcc1 exit-continuation off ;
+    [ method>> ] [ url>> [ host>> ] [ path>> ] bi ] bi 3array httpd-hit ;
 
 : split-path ( string -- path )
     "/" split harvest ;
 
-: init-request ( -- )
-    H{ } clone base-paths set
+: init-request ( request -- )
+    request set
+    H{ } clone responder-nesting set
     [ ] link-hook set
     [ ] form-hook set ;
 
+: dispatch-request ( request -- response )
+    url>> path>> split-path main-responder get call-responder ;
+
 : do-request ( request -- response )
     [
-        init-request
-        [ request set ]
+        [ init-request ]
         [ log-request ]
-        [ path>> split-path main-responder get call-responder ] tri
-        [ <404> ] unless*
-    ] [
-        [ \ do-request log-error ]
-        [ <500> ]
-        bi
-    ] recover ;
+        [ dispatch-request ] tri
+    ]
+    [ [ \ do-request log-error ] [ <500> ] bi ]
+    recover ;
 
 : ?refresh-all ( -- )
     development-mode get-global
diff --git a/extra/http/server/sessions/authors.txt b/extra/http/server/sessions/authors.txt
deleted file mode 100755 (executable)
index 7c1b2f2..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Doug Coleman
diff --git a/extra/http/server/sessions/sessions-tests.factor b/extra/http/server/sessions/sessions-tests.factor
deleted file mode 100755 (executable)
index 8ea312d..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-IN: http.server.sessions.tests\r
-USING: tools.test http http.server.sessions\r
-http.server.actions http.server math namespaces kernel accessors\r
-prettyprint io.streams.string io.files splitting destructors\r
-sequences db db.sqlite continuations ;\r
-\r
-: with-session\r
-    [\r
-        [ [ save-session-after ] [ session set ] bi ] dip call\r
-    ] with-destructors ; inline\r
-\r
-TUPLE: foo ;\r
-\r
-C: <foo> foo\r
-\r
-M: foo init-session* drop 0 "x" sset ;\r
-\r
-M: foo call-responder*\r
-    2drop\r
-    "x" [ 1+ ] schange\r
-    [ "x" sget pprint ] <html-content> ;\r
-\r
-: url-responder-mock-test\r
-    [\r
-        <request>\r
-            "GET" >>method\r
-            "id" get session-id-key set-query-param\r
-            "/" >>path\r
-        request set\r
-        { } sessions get call-responder\r
-        [ write-response-body drop ] with-string-writer\r
-    ] with-destructors ;\r
-\r
-: sessions-mock-test\r
-    [\r
-        <request>\r
-            "GET" >>method\r
-            "cookies" get >>cookies\r
-            "/" >>path\r
-        request set\r
-        { } sessions get call-responder\r
-        [ write-response-body drop ] with-string-writer\r
-    ] with-destructors ;\r
-\r
-: <exiting-action>\r
-    <action>\r
-        [ [ ] <text-content> exit-with ] >>display ;\r
-\r
-[ "auth-test.db" temp-file sqlite-db delete-file ] ignore-errors\r
-\r
-"auth-test.db" temp-file sqlite-db [\r
-\r
-    init-request\r
-    init-sessions-table\r
-\r
-    [ ] [\r
-        <foo> <sessions>\r
-        sessions set\r
-    ] unit-test\r
-\r
-    [\r
-        [ ] [\r
-            empty-session\r
-                123 >>id session set\r
-        ] unit-test\r
-\r
-        [ ] [ 3 "x" sset ] unit-test\r
-\r
-        [ 9 ] [ "x" sget sq ] unit-test\r
-\r
-        [ ] [ "x" [ 1- ] schange ] unit-test\r
-\r
-        [ 4 ] [ "x" sget sq ] unit-test\r
-\r
-        [ t ] [ session get changed?>> ] unit-test\r
-    ] with-scope\r
-\r
-    [ t ] [\r
-        begin-session id>>\r
-        get-session session?\r
-    ] unit-test\r
-\r
-    [ { 5 0 } ] [\r
-        [\r
-            begin-session\r
-            dup [ 5 "a" sset ] with-session\r
-            dup [ "a" sget , ] with-session\r
-            dup [ "x" sget , ] with-session\r
-            drop\r
-        ] { } make\r
-    ] unit-test\r
-\r
-    [ 0 ] [\r
-        begin-session id>>\r
-        get-session [ "x" sget ] with-session\r
-    ] unit-test\r
-\r
-    [ { 5 0 } ] [\r
-        [\r
-            begin-session id>>\r
-            dup get-session [ 5 "a" sset ] with-session\r
-            dup get-session [ "a" sget , ] with-session\r
-            dup get-session [ "x" sget , ] with-session\r
-            drop\r
-        ] { } make\r
-    ] unit-test\r
-\r
-    [ ] [\r
-        <foo> <sessions>\r
-        sessions set\r
-    ] unit-test\r
-\r
-    [\r
-        <request>\r
-        "GET" >>method\r
-        "/" >>path\r
-        request set\r
-        { "etc" } sessions get call-responder response set\r
-        [ "1" ] [ [ response get write-response-body drop ] with-string-writer ] unit-test\r
-        response get\r
-    ] with-destructors\r
-    response set\r
-\r
-    [ ] [ response get cookies>> "cookies" set ] unit-test\r
-\r
-    [ "2" ] [ sessions-mock-test ] unit-test\r
-    [ "3" ] [ sessions-mock-test ] unit-test\r
-    [ "4" ] [ sessions-mock-test ] unit-test\r
-\r
-    [\r
-        [ ] [\r
-            <request>\r
-                "GET" >>method\r
-                "id" get session-id-key set-query-param\r
-                "/" >>path\r
-            request set\r
-\r
-            [\r
-                { } <exiting-action> <sessions>\r
-                call-responder\r
-            ] with-destructors response set\r
-        ] unit-test\r
-\r
-        [ "text/plain" ] [ response get content-type>> ] unit-test\r
-\r
-        [ f ] [ response get cookies>> empty? ] unit-test\r
-    ] with-scope\r
-] with-db\r
diff --git a/extra/http/server/sessions/sessions.factor b/extra/http/server/sessions/sessions.factor
deleted file mode 100755 (executable)
index a7e1a14..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-! Copyright (C) 2008 Doug Coleman, Slava Pestov.
-! See http://factorcode.org/license.txt for BSD license.
-USING: assocs kernel math.intervals math.parser namespaces
-random accessors quotations hashtables sequences continuations
-fry calendar combinators destructors alarms
-db db.tuples db.types
-http http.server html.elements ;
-IN: http.server.sessions
-
-TUPLE: session id expires uid namespace changed? ;
-
-: <session> ( id -- session )
-    session new
-        swap >>id ;
-
-session "SESSIONS"
-{
-    { "id" "ID" +random-id+ system-random-generator }
-    { "expires" "EXPIRES" TIMESTAMP +not-null+ }
-    { "uid" "UID" { VARCHAR 255 } }
-    { "namespace" "NAMESPACE" FACTOR-BLOB }
-} define-persistent
-
-: get-session ( id -- session )
-    dup [ <session> select-tuple ] when ;
-
-: init-sessions-table session ensure-table ;
-
-: start-expiring-sessions ( db seq -- )
-    '[
-        , , [
-            session new
-                -1.0/0.0 now [a,b] >>expires
-            delete-tuples
-        ] with-db
-    ] 5 minutes every drop ;
-
-GENERIC: init-session* ( responder -- )
-
-M: object init-session* drop ;
-
-M: dispatcher init-session* default>> init-session* ;
-
-M: filter-responder init-session* responder>> init-session* ;
-
-TUPLE: sessions < filter-responder timeout domain ;
-
-: <sessions> ( responder -- responder' )
-    sessions new
-        swap >>responder
-        20 minutes >>timeout ;
-
-: (session-changed) ( session -- )
-    t >>changed? drop ;
-
-: session-changed ( -- )
-    session get (session-changed) ;
-
-: sget ( key -- value )
-    session get namespace>> at ;
-
-: sset ( value key -- )
-    session get
-    [ namespace>> set-at ] [ (session-changed) ] bi ;
-
-: schange ( key quot -- )
-    session get
-    [ namespace>> swap change-at ] keep
-    (session-changed) ; inline
-
-: uid ( -- uid )
-    session get uid>> ;
-
-: set-uid ( uid -- )
-    session get [ (>>uid) ] [ (session-changed) ] bi ;
-
-: init-session ( session -- )
-    session [ sessions get init-session* ] with-variable ;
-
-: cutoff-time ( -- time )
-    sessions get timeout>> from-now ;
-
-: touch-session ( session -- )
-    cutoff-time >>expires drop ;
-
-: empty-session ( -- session )
-    f <session>
-        H{ } clone >>namespace
-        dup touch-session ;
-
-: begin-session ( -- session )
-    empty-session [ init-session ] [ insert-tuple ] [ ] tri ;
-
-! Destructor
-TUPLE: session-saver session ;
-
-C: <session-saver> session-saver
-
-M: session-saver dispose
-    session>> dup changed?>> [
-        [ touch-session ] [ update-tuple ] bi
-    ] [ drop ] if ;
-
-: save-session-after ( session -- )
-    <session-saver> &dispose drop ;
-
-: existing-session ( path session -- response )
-    [ session set ] [ save-session-after ] bi
-    sessions get responder>> call-responder ;
-
-: session-id-key "factorsessid" ;
-
-: cookie-session-id ( request -- id/f )
-    session-id-key get-cookie
-    dup [ value>> string>number ] when ;
-
-: post-session-id ( request -- id/f )
-    session-id-key swap post-data>> at string>number ;
-
-: request-session-id ( -- id/f )
-    request get dup method>> {
-        { "GET" [ cookie-session-id ] }
-        { "HEAD" [ cookie-session-id ] }
-        { "POST" [ post-session-id ] }
-    } case ;
-
-: request-session ( -- session/f )
-    request-session-id get-session ;
-
-: <session-cookie> ( id -- cookie )
-    session-id-key <cookie>
-        "$sessions" resolve-base-path >>path
-        sessions get timeout>> from-now >>expires
-        sessions get domain>> >>domain ;
-
-: put-session-cookie ( response -- response' )
-    session get id>> number>string <session-cookie> put-cookie ;
-
-: session-form-field ( -- )
-    <input
-        "hidden" =type
-        session-id-key =name
-        session get id>> number>string =value
-    input/> ;
-
-M: sessions call-responder* ( path responder -- response )
-    [ session-form-field ] add-form-hook
-    sessions set
-    request-session [ begin-session ] unless*
-    existing-session put-session-cookie ;
-
-: logout-all-sessions ( uid -- )
-    session new swap >>uid delete-tuples ;
index 8814004589529f0e308b378e608f40d2fb58e657..d64268d68e9bcbdb94313a7ba08ef5c9fa93d51a 100755 (executable)
@@ -4,7 +4,7 @@ USING: calendar io io.files kernel math math.order
 math.parser http http.server namespaces parser sequences strings\r
 assocs hashtables debugger http.mime sorting html.elements\r
 html.templates.fhtml logging calendar.format accessors\r
-io.encodings.binary fry xml.entities destructors ;\r
+io.encodings.binary fry xml.entities destructors urls ;\r
 IN: http.server.static\r
 \r
 ! special maps mime types to quots with effect ( path -- )\r
@@ -71,7 +71,7 @@ TUPLE: file-responder root hook special allow-listings ;
 \r
 : list-directory ( directory -- response )\r
     file-responder get allow-listings>> [\r
-        '[ , directory. ] <html-content>\r
+        '[ , directory. ] "text/html" <content>\r
     ] [\r
         drop <403>\r
     ] if ;\r
@@ -85,7 +85,7 @@ TUPLE: file-responder root hook special allow-listings ;
         find-index [ serve-file ] [ list-directory ] ?if\r
     ] [\r
         drop\r
-        request get path>> "/" append f <standard-redirect>\r
+        request get url>> clone [ "/" append ] change-path <redirect>\r
     ] if ;\r
 \r
 : serve-object ( filename -- response )\r
@@ -101,6 +101,6 @@ M: file-responder call-responder* ( path responder -- response )
 \r
 ! file responder integration\r
 : enable-fhtml ( responder -- responder )\r
-    [ <fhtml> <html-content> ]\r
+    [ <fhtml> "text/html" <content> ]\r
     "application/x-factor-server-page"\r
     pick special>> set-at ;\r
index 7ee14e03e5d3200b26d06041c471dbc689191931..033ba3cbfb12e6465d0c56bdbdb02702683b06a5 100644 (file)
@@ -1,13 +1,22 @@
 ! Copyright (C) 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: accessors kernel arrays namespaces sequences continuations
-destructors io.sockets ;
+destructors io.sockets alien alien.syntax ;
 IN: io.pools
 
-TUPLE: pool connections disposed ;
+TUPLE: pool connections disposed expired ;
+
+: check-pool ( pool -- )
+    dup check-disposed
+    dup expired>> expired? [
+        ALIEN: 31337 >>expired
+        connections>> [ delete-all ] [ dispose-each ] bi
+    ] [ drop ] if ;
 
 : <pool> ( class -- pool )
-    new V{ } clone >>connections ; inline
+    new V{ } clone
+        >>connections
+        dup check-pool ; inline
 
 M: pool dispose* connections>> dispose-each ;
 
@@ -17,15 +26,14 @@ M: pool dispose* connections>> dispose-each ;
 TUPLE: return-connection conn pool ;
 
 : return-connection ( conn pool -- )
-    dup check-disposed connections>> push ;
+    dup check-pool connections>> push ;
 
 GENERIC: make-connection ( pool -- conn )
 
 : new-connection ( pool -- )
-    [ make-connection ] keep return-connection ;
+    dup check-pool [ make-connection ] keep return-connection ;
 
 : acquire-connection ( pool -- conn )
-    dup check-disposed
     [ dup connections>> empty? ] [ dup new-connection ] [ ] while
     connections>> pop ;
 
index a8f649e2c9172c430fcfe9e0566c2bcdad3ade31..754e69a476fcd5f6ed14eb5ecf2d3a08b8753cbf 100644 (file)
@@ -38,7 +38,7 @@ M: delete diff-line
     </tr> ;
 
 : htmlize-diff ( diff -- )
-    <table "comparison" =class table>
+    <table "100%" =width "comparison" =class table>
         <tr> <th> "Old" write </th> <th> "New" write </th> </tr>
         [ diff-line ] each
     </table> ;
index 364c24b91fb9f85ee0aae10704e2830d76a485ae..5183af51450da2486aa025ab3d89f9c9da8ab164 100644 (file)
@@ -4,7 +4,7 @@ USING: xml.utilities kernel assocs xml.generator math.order
     strings sequences xml.data xml.writer
     io.streams.string combinators xml xml.entities io.files io
     http.client namespaces xml.generator hashtables
-    calendar.format accessors continuations ;
+    calendar.format accessors continuations urls ;
 IN: rss
 
 : any-tag-named ( tag names -- tag-inside )
@@ -103,18 +103,15 @@ C: <entry> entry
 
 : entry, ( entry -- )
     "entry" [
-        dup entry-title "title" { { "type" "html" } } simple-tag*,
-        "link" over entry-link "href" associate contained*,
-        dup entry-pub-date timestamp>rfc3339 "published" simple-tag,
-        entry-description [ "content" { { "type" "html" } } simple-tag*, ] when*
+        dup title>> "title" { { "type" "html" } } simple-tag*,
+        "link" over link>> dup url? [ url>string ] when "href" associate contained*,
+        dup pub-date>> timestamp>rfc3339 "published" simple-tag,
+        description>> [ "content" { { "type" "html" } } simple-tag*, ] when*
     ] tag, ;
 
 : feed>xml ( feed -- xml )
     "feed" { { "xmlns" "http://www.w3.org/2005/Atom" } } [
-        dup feed-title "title" simple-tag,
-        "link" over feed-link "href" associate contained*,
-        feed-entries [ entry, ] each
+        dup title>> "title" simple-tag,
+        "link" over link>> dup url? [ url>string ] when "href" associate contained*,
+        entries>> [ entry, ] each
     ] make-xml* ;
-
-: write-feed ( feed -- )
-    feed>xml write-xml ;
index 8a4c6146deb7af57c79b3370632f3af0037de173..f020724d312bab0474bcfbf7ab0ed2930ed7b005 100644 (file)
@@ -1,6 +1,6 @@
 ! Copyright (C) 2008 Alex Chapman
 ! See http://factorcode.org/license.txt for BSD license.
-USING: accessors assocs db db.sqlite db.postgresql http http.server http.server.actions http.server.static io io.files json.writer kernel math.parser namespaces semantic-db sequences strings tangle.path ;
+USING: accessors assocs db db.sqlite db.postgresql http http.server furnace.actions http.server.static io io.files json.writer kernel math.parser namespaces semantic-db sequences strings tangle.path ;
 IN: tangle
 
 GENERIC: render* ( content templater -- output )
@@ -20,7 +20,7 @@ C: <tangle> tangle
     [ [ db>> ] [ seq>> ] bi ] dip with-db ;
 
 : node-response ( id -- response )
-    load-node [ node-content <text-content> ] [ <404> ] if* ;
+    load-node [ node-content "text/plain" <content> ] [ <404> ] if* ;
 
 : display-node ( params -- response )
     [
@@ -36,7 +36,7 @@ C: <tangle> tangle
 : submit-node ( params -- response )
     [
         "node_content" swap at* [
-            create-node id>> number>string <text-content>
+            create-node id>> number>string "text/plain" <content>
         ] [
             drop <400>
         ] if
@@ -52,7 +52,7 @@ TUPLE: path-responder ;
 C: <path-responder> path-responder
 
 M: path-responder call-responder* ( path responder -- response )
-    drop path>file [ node-content <text-content> ] [ <404> ] if* ;
+    drop path>file [ node-content "text/plain" <content> ] [ <404> ] if* ;
 
 TUPLE: tangle-dispatcher < dispatcher tangle ;
 
index e28816fdb319a1da2dc4614467fd7f22f22c34e0..e64ef283c5930d114de4c5570e332e3703b7b515 100644 (file)
@@ -77,10 +77,17 @@ USING: urls tools.test tuple-syntax arrays kernel assocs ;
             }
             "a/relative/path"
         }
+        {
+            TUPLE{ url
+                path: "bar"
+                query: H{ { "a" "b" } }
+            }
+            "bar?a=b"
+        }
     } ;
 
 urls [
-    [ 1array ] [ [ string>url ] curry ] bi* unit-test
+    [ 1array ] [ [ >url ] curry ] bi* unit-test
 ] assoc-each
 
 urls [
@@ -192,3 +199,7 @@ urls [
 
     derive-url
 ] unit-test
+
+[ "a" ] [
+    <url> "a" "b" set-query-param "b" query-param
+] unit-test
index e20df6565640b1f649df10ee34b3d801bda1ca84..472eead0f22423b3cbab677bf9797fc3b0864e24 100644 (file)
@@ -1,9 +1,9 @@
 ! Copyright (C) 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: kernel unicode.categories combinators sequences splitting
-fry namespaces assocs arrays strings mirrors
-io.encodings.string io.encodings.utf8
-math math.parser accessors namespaces.lib ;
+fry namespaces assocs arrays strings io.encodings.string
+io.encodings.utf8 math math.parser accessors mirrors parser
+prettyprint.backend hashtables ;
 IN: urls
 
 : url-quotable? ( ch -- ? )
@@ -91,11 +91,13 @@ IN: urls
 
 TUPLE: url protocol host port path query anchor ;
 
+: <url> ( -- url ) url new ;
+
 : query-param ( request key -- value )
     swap query>> at ;
 
 : set-query-param ( request value key -- request )
-    pick query>> set-at ;
+    '[ , , _ ?set-at ] change-query ;
 
 : parse-host ( string -- host port )
     ":" split1 [ url-decode ] [
@@ -105,40 +107,44 @@ TUPLE: url protocol host port path query anchor ;
         ] when
     ] bi* ;
 
-: parse-host-part ( protocol rest -- string' )
-    [ "protocol" set ] [
+: parse-host-part ( url protocol rest -- url string' )
+    [ >>protocol ] [
         "//" ?head [ "Invalid URL" throw ] unless
         "/" split1 [
-            parse-host [ "host" set ] [ "port" set ] bi*
+            parse-host [ >>host ] [ >>port ] bi*
         ] [ "/" prepend ] bi*
     ] bi* ;
 
-: string>url ( string -- url )
-    [
-        ":" split1 [ parse-host-part ] when*
-        "#" split1 [
-            "?" split1 [ query>assoc "query" set ] when*
-            url-decode "path" set
-        ] [
-            url-decode "anchor" set
-        ] bi*
-    ] url make-object ;
+GENERIC: >url ( obj -- url )
+
+M: url >url ;
 
-: unparse-host-part ( protocol -- )
+M: string >url
+    <url> swap
+    ":" split1 [ parse-host-part ] when*
+    "#" split1 [
+        "?" split1
+        [ url-decode >>path ]
+        [ [ query>assoc >>query ] when* ] bi*
+    ]
+    [ url-decode >>anchor ] bi* ;
+
+: unparse-host-part ( url protocol -- )
     %
     "://" %
-    "host" get url-encode %
-    "port" get [ ":" % # ] when*
-    "path" get "/" head? [ "Invalid URL" throw ] unless ;
+    [ host>> url-encode % ]
+    [ port>> [ ":" % # ] when* ]
+    [ path>> "/" head? [ "/" % ] unless ]
+    tri ;
 
 : url>string ( url -- string )
     [
-        <mirror> [
-            "protocol" get [ unparse-host-part ] when*
-            "path" get url-encode %
-            "query" get [ "?" % assoc>query % ] when*
-            "anchor" get [ "#" % url-encode % ] when*
-        ] bind
+        {
+            [ dup protocol>> dup [ unparse-host-part ] [ 2drop ] if ]
+            [ path>> url-encode % ]
+            [ query>> dup assoc-empty? [ drop ] [ "?" % assoc>query % ] if ]
+            [ anchor>> [ "#" % url-encode % ] when* ]
+        } cleave
     ] "" make ;
 
 : url-append-path ( path1 path2 -- path )
@@ -158,3 +164,7 @@ TUPLE: url protocol host port path query anchor ;
 
 : relative-url ( url -- url' )
     clone f >>protocol f >>host f >>port ;
+
+: URL" lexer get skip-blank parse-string >url parsed ; parsing
+
+M: url pprint* dup url>string "URL\" " "\"" pprint-string ;
index 04194adb293a7cb81d38991b0f98c3dee7be12a6..29ce3f0e7c716fc50ee813898dc077004417fbb4 100644 (file)
@@ -1,6 +1,6 @@
-USING: math kernel accessors html.components
-http.server http.server.actions
-http.server.sessions html.templates.chloe fry ;
+USING: math kernel accessors html.components http.server
+furnace.actions furnace.sessions html.templates.chloe
+fry urls ;
 IN: webapps.counter
 
 SYMBOL: count
@@ -11,15 +11,15 @@ M: counter-app init-session* drop 0 count sset ;
 
 : <counter-action> ( quot -- action )
     <action>
-        swap '[ count , schange "" f <standard-redirect> ] >>submit ;
-
-: counter-template ( -- template )
-    "resource:extra/webapps/counter/counter.xml" <chloe> ;
+        swap '[
+            count , schange
+            URL" $counter-app" <redirect>
+        ] >>submit ;
 
 : <display-action> ( -- action )
     <page-action>
         [ count sget "counter" set-value ] >>init
-        counter-template >>template ;
+        "$counter-app/counter" >>template ;
 
 : <counter-app> ( -- responder )
     counter-app new-dispatcher
index 9ad4a054922c01bf14819f2e3ea487d1656168b2..5565625a9c30565978e2d050f5f4618efdd83f22 100644 (file)
@@ -4,13 +4,12 @@ USING: accessors kernel sequences assocs io.files io.sockets
 io.server
 namespaces db db.sqlite smtp
 http.server
-http.server.db
-http.server.flows
-http.server.sessions
-http.server.auth.login
-http.server.auth.providers.db
-http.server.boilerplate
-html.templates.chloe
+furnace.db
+furnace.flows
+furnace.sessions
+furnace.auth.login
+furnace.auth.providers.db
+furnace.boilerplate
 webapps.pastebin
 webapps.planet
 webapps.todo
@@ -20,9 +19,6 @@ IN: webapps.factor-website
 
 : test-db "resource:test.db" sqlite-db ;
 
-: factor-template ( path -- template )
-    "resource:extra/webapps/factor-website/" swap ".xml" 3append <chloe> ;
-
 : init-factor-db ( -- )
     test-db [
         init-users-table
@@ -40,8 +36,10 @@ IN: webapps.factor-website
         init-revisions-table
     ] with-db ;
 
+TUPLE: factor-website < dispatcher ;
+
 : <factor-website> ( -- responder )
-    <dispatcher> 
+    factor-website new-dispatcher 
         <todo-list> "todo" add-responder
         <pastebin> "pastebin" add-responder
         <planet-factor> "planet" add-responder
@@ -53,7 +51,7 @@ IN: webapps.factor-website
         allow-password-recovery
         allow-edit-profile
     <boilerplate>
-        "page" factor-template >>template
+        "$factor-website/page" >>template
     <flows>
     <sessions>
     test-db <db-persistence> ;
index f7080643b448f7cf190f9264c2429f2610eb6283..32e1223c587376800290daf8a924d8001bba36b1 100644 (file)
@@ -15,6 +15,8 @@
                        <t:style t:include="resource:extra/webapps/factor-website/page.css" />
 
                        <t:write-style />
+
+                       <t:write-atom />
                </head>
 
                <body>
index 57c2fdb7c2b27418dab789eb7efc817a012e60e8..9f35d83fd8d4e18f583c87723f27d0062ab3b6ff 100644 (file)
@@ -2,7 +2,7 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-       <t:atom t:title="Paste - Atom" t:href="$pastebin/paste.atom" t:query="id" />
+       <t:atom t:title="This paste" t:href="$pastebin/paste.atom" t:query="id" />
 
        <t:title>Paste: <t:label t:name="summary" /></t:title>
 
                <tr><th class="field-label">Date: </th><td><t:label t:name="date" /></td></tr>
        </table>
 
-       <pre class="description"><t:code t:name="contents" t:mode="modes" /></pre>
+       <pre class="description"><t:code t:name="contents" t:mode="mode" /></pre>
 
        <t:button t:action="$pastebin/delete-paste" t:for="id" class="link-button link">Delete Paste</t:button>
-       |
-       <t:a t:href="$pastebin/annotate" t:query="id">Annotate</t:a>
 
-       <t:each-tuple t:values="annotations">
+       <t:bind-each t:name="annotations">
 
-               <h2>Annotation: <t:label t:name="summary" /></h2>
+               <a name="@id"><h2>Annotation: <t:label t:name="summary" /></h2></a>
 
                <table>
                        <tr><th class="field-label">Author: </th><td><t:label t:name="author" /></td></tr>
@@ -32,9 +30,9 @@
 
                <t:button t:action="$pastebin/delete-annotation" t:for="aid" class="link-button link">Delete Annotation</t:button>
 
-       </t:each-tuple>
+       </t:bind-each>
 
-       <t:bind-assoc t:name="new-annotation">
+       <t:bind t:name="new-annotation">
 
                <h2>New Annotation</h2>
 
@@ -55,6 +53,6 @@
                        <input type="SUBMIT" value="Done" />
                </t:form>
 
-       </t:bind-assoc>
+       </t:bind>
 
 </t:chloe>
index f785fceb6b99e61042be8ad14b1dc2db7a7c72d5..a86404d45109bbe9dfa09bd007c4b83bc20b2ba9 100644 (file)
@@ -2,6 +2,8 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
+       <t:atom t:title="Pastebin" t:href="$pastebin/list.atom" />
+
        <t:style t:include="resource:extra/webapps/pastebin/pastebin.css" />
 
        <div class="navbar">
index 43cae74ec870876a30faea7e72ffdef573fe6198..b2bcc685df915cb947628fcd40d6d9e89c24e03a 100644 (file)
@@ -2,13 +2,13 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: namespaces assocs sorting sequences kernel accessors
 hashtables sequences.lib db.types db.tuples db combinators
-calendar calendar.format math.parser rss xml.writer
+calendar calendar.format math.parser rss urls xml.writer
 xmode.catalog validators html.components html.templates.chloe
 http.server
-http.server.actions
-http.server.auth
-http.server.auth.login
-http.server.boilerplate ;
+furnace.actions
+furnace.auth
+furnace.auth.login
+furnace.boilerplate ;
 IN: webapps.pastebin
 
 ! ! !
@@ -58,28 +58,31 @@ annotation "ANNOTATIONS"
 : paste ( id -- paste )
     <paste> select-tuple fetch-annotations ;
 
-: <id-redirect> ( id next -- response )
-    swap "id" associate <standard-redirect> ;
-
 ! ! !
 ! LINKS, ETC
 ! ! !
 
 : pastebin-link ( -- url )
-    "$pastebin/list" f link>string ;
+    URL" $pastebin/list" ;
 
 GENERIC: entity-link ( entity -- url )
 
+: paste-link ( id -- url )
+    <url>
+        "$pastebin/paste" >>path
+        swap "id" set-query-param ;
+
 M: paste entity-link
-    id>> "id" associate "$pastebin/paste" swap link>string ;
+    id>> paste-link ;
 
-M: annotation entity-link
-    [ parent>> "parent" associate "$pastebin/paste" swap link>string ]
-    [ id>> number>string "#" prepend ] bi
-    append ;
+: annotation-link ( parent id -- url )
+    <url>
+        "$pastebin/paste" >>path
+        swap number>string >>anchor
+        swap "id" set-query-param ;
 
-: pastebin-template ( name -- template )
-    "resource:extra/webapps/pastebin/" swap ".xml" 3append <chloe> ;
+M: annotation entity-link
+    [ parent>> ] [ id>> ] bi annotation-link ;
 
 ! ! !
 ! PASTE LIST
@@ -88,7 +91,7 @@ M: annotation entity-link
 : <pastebin-action> ( -- action )
     <page-action>
         [ pastes "pastes" set-value ] >>init
-        "pastebin" pastebin-template >>template ;
+        "$pastebin/pastebin" >>template ;
 
 : pastebin-feed-entries ( seq -- entries )
     <reversed> 20 short head [
@@ -96,7 +99,7 @@ M: annotation entity-link
             swap
             [ summary>> >>title ]
             [ date>> >>pub-date ]
-            [ entity-link >>link ]
+            [ entity-link adjust-url >>link ]
             tri
     ] map ;
 
@@ -117,7 +120,7 @@ M: annotation entity-link
     <page-action>
         [
             validate-integer-id
-            "id" value paste from-tuple
+            "id" value paste from-object
 
             "id" value
             "new-annotation" [
@@ -127,7 +130,7 @@ M: annotation entity-link
             ] nest-values
         ] >>init
 
-        "paste" pastebin-template >>template ;
+        "$pastebin/paste" >>template ;
 
 : paste-feed-entries ( paste -- entries )
     fetch-annotations annotations>> pastebin-feed-entries ;
@@ -135,15 +138,15 @@ M: annotation entity-link
 : paste-feed ( paste -- feed )
     feed new
         swap
-        [ "Paste #" swap id>> number>string append >>title ]
-        [ entity-link >>link ]
+        [ "Paste " swap id>> number>string append >>title ]
+        [ entity-link adjust-url >>link ]
         [ paste-feed-entries >>entries ]
         tri ;
 
 : <paste-feed-action> ( -- action )
     <feed-action>
         [ validate-integer-id ] >>init
-        [ "id" value paste annotations>> paste-feed ] >>feed ;
+        [ "id" value paste paste-feed ] >>feed ;
 
 : validate-entity ( -- )
     {
@@ -165,7 +168,7 @@ M: annotation entity-link
             mode-names "modes" set-value
         ] >>init
 
-        "new-paste" pastebin-template >>template
+        "$pastebin/new-paste" >>template
 
         [
             validate-entity
@@ -173,7 +176,7 @@ M: annotation entity-link
             f <paste>
             [ deposit-entity-slots ]
             [ insert-tuple ]
-            [ id>> "$pastebin/paste" <id-redirect> ]
+            [ id>> paste-link <redirect> ]
             tri
         ] >>submit ;
 
@@ -184,7 +187,7 @@ M: annotation entity-link
         [
             "id" value <paste> delete-tuples
             "id" value f <annotation> delete-tuples
-            "$pastebin/list" f <permanent-redirect>
+            URL" $pastebin/list" <redirect>
         ] >>submit ;
 
 ! ! !
@@ -192,10 +195,10 @@ M: annotation entity-link
 ! ! !
 
 : <new-annotation-action> ( -- action )
-    <page-action>
+    <action>
         [
             { { "id" [ v-integer ] } } validate-params
-            "id" value "$pastebin/paste" <id-redirect>
+            "id" value paste-link <redirect>
         ] >>display
 
         [
@@ -207,10 +210,7 @@ M: annotation entity-link
             "id" value f <annotation>
             [ deposit-entity-slots ]
             [ insert-tuple ]
-            [
-                ! Add anchor here
-                parent>> "$pastebin/paste" <id-redirect>
-            ]
+            [ entity-link <redirect> ]
             tri
         ] >>submit ;
 
@@ -221,7 +221,7 @@ M: annotation entity-link
         [
             f "id" value <annotation> select-tuple
             [ delete-tuples ]
-            [ parent>> "$pastebin/paste" <id-redirect> ]
+            [ parent>> paste-link <redirect> ]
             bi
         ] >>submit ;
 
@@ -242,7 +242,7 @@ can-delete-pastes? define-capability
         <new-annotation-action> "new-annotation" add-responder
         <delete-annotation-action> { can-delete-pastes? } <protected> "delete-annotation" add-responder
     <boilerplate>
-        "pastebin-common" pastebin-template >>template ;
+        "$pastebin/pastebin-common" >>template ;
 
 : init-pastes-table \ paste ensure-table ;
 
index 9ec2cb7976dca830ec746ab3a059a5e25a5bc8f4..a6b3078aa793fb5f88432aa3422d3a2b91d96979 100644 (file)
@@ -2,8 +2,6 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-       <t:atom t:title="Pastebin - Atom" t:href="$pastebin/list.atom" />
-
        <t:title>Pastebin</t:title>
 
        <table width="100%">
                <th align="left" width="100">Paste by:</th>
                <th align="left" width="200">Date:</th>
 
-               <t:each-tuple t:values="pastes">
+               <t:bind-each t:name="pastes">
                        <tr>
                                <td><t:a t:href="$pastebin/paste" t:query="id"><t:label t:name="summary" /></t:a></td>
                                <td><t:label t:name="author" /></td>
                                <td><t:label t:name="date" /></td>
                        </tr>
-               </t:each-tuple>
+               </t:bind-each>
        </table>
 
 </t:chloe>
index 4711ca4716d5ea1db834e313a31897edc9066f6f..26a3e6f2066824330fb4638c0bdb7607421027c0 100644 (file)
@@ -5,13 +5,13 @@
        <t:title>Planet Factor Administration</t:title>
 
        <ul>
-               <t:each-tuple t:values="blogroll">
+               <t:bind-each t:name="blogroll">
                        <li>
                                <t:a t:href="$planet-factor/admin/edit-blog" t:query="id">
                                        <t:label t:name="name" />
                                </t:a>
                        </li>
-               </t:each-tuple>
+               </t:bind-each>
        </ul>
 
        <p>
index 1338463bcf090479f5ba0974d73f4c564341ff36..7c5269b8d91d9c835a250f4456834b18af752f0c 100644 (file)
@@ -2,13 +2,13 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-       <t:each-tuple t:values="postings">
+       <t:bind-each t:name="postings">
 
                <p class="news">
                        <strong><t:view t:component="title" /></strong> <br/>
                        <t:a value="link" t:session="none" class="more">Read More...</t:a>
                </p>
 
-       </t:each-tuple>
+       </t:bind-each>
 
 </t:chloe>
index 414a59f3b2a1aa97b0c78a04f0f69cdd20b3edf3..39539441ce37184f2bd87994f508b5c321b776e8 100755 (executable)
@@ -3,19 +3,16 @@
 USING: kernel accessors sequences sorting math math.order
 calendar alarms logging concurrency.combinators namespaces
 sequences.lib db.types db.tuples db fry locals hashtables
-html.components html.templates.chloe
-rss xml.writer
+html.components
+rss urls xml.writer
 validators
 http.server
-http.server.actions
-http.server.boilerplate
-http.server.auth.login
-http.server.auth ;
+furnace.actions
+furnace.boilerplate
+furnace.auth.login
+furnace.auth ;
 IN: webapps.planet
 
-: planet-template ( name -- template )
-    "resource:extra/webapps/planet/" swap ".xml" 3append <chloe> ;
-
 TUPLE: blog id name www-url feed-url ;
 
 M: blog link-title name>> ;
@@ -61,7 +58,7 @@ posting "POSTINGS"
 : <edit-blogroll-action> ( -- action )
     <page-action>
         [ blogroll "blogroll" set-value ] >>init
-        "admin" planet-template >>template ;
+        "$planet-factor/admin" >>template ;
 
 : <planet-action> ( -- action )
     <page-action>
@@ -70,7 +67,7 @@ posting "POSTINGS"
             postings "postings" set-value
         ] >>init
 
-        "planet" planet-template >>template ;
+        "$planet-factor/planet" >>template ;
 
 : planet-feed ( -- feed )
     feed new
@@ -110,7 +107,7 @@ posting "POSTINGS"
     <action>
         [
             update-cached-postings
-            "" f <permanent-redirect>
+            URL" $planet-factor/admin" <redirect>
         ] >>submit ;
 
 : <delete-blog-action> ( -- action )
@@ -119,7 +116,7 @@ posting "POSTINGS"
 
         [
             "id" value <blog> delete-tuples
-            "$planet-factor/admin" f <standard-redirect>
+            URL" $planet-factor/admin" <redirect>
         ] >>submit ;
 
 : validate-blog ( -- )
@@ -129,15 +126,12 @@ posting "POSTINGS"
         { "feed-url" [ v-url ] }
     } validate-params ;
 
-: <id-redirect> ( id next -- response )
-    swap "id" associate <standard-redirect> ;
-
 : deposit-blog-slots ( blog -- )
     { "name" "www-url" "feed-url" } deposit-slots ;
 
 : <new-blog-action> ( -- action )
     <page-action>
-        "new-blog" planet-template >>template
+        "$planet-factor/new-blog" >>template
 
         [ validate-blog ] >>validate
 
@@ -145,7 +139,12 @@ posting "POSTINGS"
             f <blog>
             [ deposit-blog-slots ]
             [ insert-tuple ]
-            [ id>> "$planet-factor/admin/edit-blog" <id-redirect> ]
+            [
+                <url>
+                    "$planet-factor/admin/edit-blog" >>path
+                    swap id>> "id" set-query-param
+                <redirect>
+            ]
             tri
         ] >>submit ;
     
@@ -153,10 +152,10 @@ posting "POSTINGS"
     <page-action>
         [
             validate-integer-id
-            "id" value <blog> select-tuple from-tuple
+            "id" value <blog> select-tuple from-object
         ] >>init
 
-        "edit-blog" planet-template >>template
+        "$planet-factor/edit-blog" >>template
 
         [
             validate-integer-id
@@ -167,7 +166,12 @@ posting "POSTINGS"
             f <blog>
             [ deposit-blog-slots ]
             [ update-tuple ]
-            [ id>> "$planet-factor/admin" <id-redirect> ]
+            [
+                <url>
+                    "$planet-factor/admin" >>path
+                    swap id>> "id" set-query-param
+                <redirect>
+            ]
             tri
         ] >>submit ;
 
@@ -193,7 +197,7 @@ TUPLE: planet-factor < dispatcher ;
         <feed-action> "feed.xml" add-responder
         <planet-factor-admin> { can-administer-planet-factor? } <protected> "admin" add-responder
     <boilerplate>
-        "planet-common" planet-template >>template ;
+        "$planet-factor/planet-common" >>template ;
 
 : start-update-task ( db params -- )
     '[ , , [ update-cached-postings ] with-db ] 10 minutes every drop ;
index 526a9b306b6af7d3e98ade9fb3957203c7f48b46..4ee1c171e2fa5c901d8053693e9e947bbcb841b2 100644 (file)
@@ -8,7 +8,7 @@
                <tr>
                        <td>
 
-                               <t:each-tuple t:values="postings">
+                               <t:bind-each t:name="postings">
 
                                        <h2 class="posting-title">
                                                <t:a t:value="link" t:session="none"><t:label t:name="title" /></t:a>
@@ -22,7 +22,7 @@
                                                <t:a t:value="link" t:session="none"><t:label t:name="pub-date" /></t:a>
                                        </p>
 
-                               </t:each-tuple>
+                               </t:bind-each>
 
                        </td>
 
@@ -31,7 +31,7 @@
                                <h2>Blogroll</h2>
 
                                <ul>
-                                       <t:each t:values="blogroll">
+                                       <t:each t:name="blogroll">
                                                <li>
                                                        <t:link t:name="value"/>
                                                </li>
index 0974c8ce1bb7bddeb97fa39b3d5eef14ed1bc75d..6bae6e705e8d6aef682e38c51b7fe21c2e6f9517 100644 (file)
                <input type="SUBMIT" value="Done" />
        </t:form>
 
-       <t:if t:value="id">
-       
-               <t:a t:href="$todo-list/view" t:query="id">View</t:a>
-               |
-               <t:button t:action="$todo-list/delete" t:for="id" class="link-button link">Delete</t:button>
-               
-       </t:if>
+       <t:a t:href="$todo-list/view" t:query="id">View</t:a>
+       |
+       <t:button t:action="$todo-list/delete" t:for="id" class="link-button link">Delete</t:button>
 
 </t:chloe>
diff --git a/extra/webapps/todo/new-todo.xml b/extra/webapps/todo/new-todo.xml
new file mode 100644 (file)
index 0000000..f557d53
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+       <t:title>New Item</t:title>
+
+       <t:form t:action="$todo-list/new">
+               <table>
+                       <tr><th class="field-label">Summary: </th><td><t:field t:name="summary" /></td></tr>
+                       <tr><th class="field-label">Priority: </th><td><t:field t:name="priority" /></td></tr>
+                       <tr><th class="field-label big-field-label">Description:</th><td><t:textarea t:name="description" t:rows="20" t:cols="60" /></td></tr>
+               </table>
+
+               <input type="SUBMIT" value="Done" />
+       </t:form>
+
+</t:chloe>
index 845c38dbf7c9f6d354ca88f598c477f6d78a886b..036c59030646320f878605de7ace2632f438408f 100644 (file)
@@ -13,7 +13,7 @@
                        <th>Edit</th>
                </tr>
 
-               <t:each-tuple t:values="items">
+               <t:bind-each t:name="items">
 
                        <tr>
                                <td>
@@ -30,7 +30,7 @@
                                </td>
                        </tr>
 
-               </t:each-tuple>
+               </t:bind-each>
 
        </table>
 
index e3b174eaea76afd66047288c43ca86b0109ca10e..063c8515f7f2ece6e914bbc8b2c9df1f3deb3891 100755 (executable)
@@ -1,15 +1,15 @@
 ! Copyright (c) 2008 Slava Pestov
 ! See http://factorcode.org/license.txt for BSD license.
 USING: accessors kernel sequences namespaces
-db db.types db.tuples validators hashtables
+db db.types db.tuples validators hashtables urls
 html.components
 html.templates.chloe
-http.server.sessions
-http.server.boilerplate
-http.server.auth
-http.server.actions
-http.server.db
-http.server.auth.login
+furnace.sessions
+furnace.boilerplate
+furnace.auth
+furnace.actions
+furnace.db
+furnace.auth.login
 http.server ;
 IN: webapps.todo
 
@@ -31,20 +31,14 @@ todo "TODO"
         swap >>id
         uid >>uid ;
 
-: todo-template ( name -- template )
-    "resource:extra/webapps/todo/" swap ".xml" 3append <chloe> ;
-
 : <view-action> ( -- action )
     <page-action>
         [
             validate-integer-id
-            "id" value <todo> select-tuple from-tuple
+            "id" value <todo> select-tuple from-object
         ] >>init
         
-        "view-todo" todo-template >>template ;
-
-: <id-redirect> ( id next -- response )
-    swap "id" associate <standard-redirect> ;
+        "$todo-list/view-todo" >>template ;
 
 : validate-todo ( -- )
     {
@@ -57,15 +51,20 @@ todo "TODO"
     <page-action>
         [ 0 "priority" set-value ] >>init
 
-        "edit-todo" todo-template >>template
+        "$todo-list/new-todo" >>template
 
         [ validate-todo ] >>validate
 
         [
             f <todo>
-                dup { "summary" "description" } deposit-slots
+                dup { "summary" "priority" "description" } deposit-slots
             [ insert-tuple ]
-            [ id>> "$todo-list/view" <id-redirect> ]
+            [
+                <url>
+                    "$todo-list/view" >>path
+                    swap id>> "id" set-query-param
+                <redirect>
+            ]
             bi
         ] >>submit ;
 
@@ -73,10 +72,10 @@ todo "TODO"
     <page-action>
         [
             validate-integer-id
-            "id" value <todo> select-tuple from-tuple
+            "id" value <todo> select-tuple from-object
         ] >>init
 
-        "edit-todo" todo-template >>template
+        "$todo-list/edit-todo" >>template
 
         [
             validate-integer-id
@@ -87,7 +86,12 @@ todo "TODO"
             f <todo>
                 dup { "id" "summary" "priority" "description" } deposit-slots
             [ update-tuple ]
-            [ id>> "$todo-list/view" <id-redirect> ]
+            [
+                <url>
+                    "$todo-list/view" >>path
+                    swap id>> "id" set-query-param
+                <redirect>
+            ]
             bi
         ] >>submit ;
 
@@ -97,13 +101,13 @@ todo "TODO"
 
         [
             "id" get <todo> delete-tuples
-            "$todo-list/list" f <standard-redirect>
+            URL" $todo-list/list" <redirect>
         ] >>submit ;
 
 : <list-action> ( -- action )
     <page-action>
         [ f <todo> select-tuples "items" set-value ] >>init
-        "todo-list" todo-template >>template ;
+        "$todo-list/todo-list" >>template ;
 
 TUPLE: todo-list < dispatcher ;
 
@@ -115,5 +119,5 @@ TUPLE: todo-list < dispatcher ;
         <edit-action>   "edit"   add-responder
         <delete-action> "delete" add-responder
     <boilerplate>
-        "todo" todo-template >>template
+        "$todo-list/todo" >>template
     f <protected> ;
index 39ab5cda8b9597e06099c73bf8bcf8b065a7420b..e892137932bdb9af300d79bc014d9c7128630a81 100644 (file)
@@ -6,7 +6,7 @@
 
        <div class="navbar">
                  <t:a t:href="$todo-list/list">List Items</t:a>
-               | <t:a t:href="$todo-list/edit">Add Item</t:a>
+               | <t:a t:href="$todo-list/new">Add Item</t:a>
 
                <t:if t:code="http.server.auth.login:allow-edit-profile?">
                        | <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
index 3f9ac8d69082732bd6f021992815e0cd890e606c..0c55f8ca76dbe8bceb1b0f297063cd85e662161e 100644 (file)
        
        <tr>
                <th class="field-label big-field-label">Capabilities:</th>
-               <td><t:choice t:name="capabilities" t:choices="all-capabilities" t:multiple="true" /></td>
+               <td>
+                       <t:each t:name="capabilities">
+                               <t:checkbox t:name="@value" t:label="@value" /><br/>
+                       </t:each>
+               </td>
        </tr>
        
        <tr>
index 881dca9c168a5a492446d3052922e6b0272542ae..b1f35c979b4954feee18bfbd3e49084e656f09e5 100644 (file)
        
        <tr>
                <th class="field-label big-field-label">Capabilities:</th>
-               <td><t:choice t:name="capabilities" t:choices="all-capabilities" t:multiple="true" /></td>
+               <td>
+                       <t:each t:name="capabilities">
+                               <li><t:checkbox t:name="@value" t:label="@value" /><br/>
+                       </t:each>
+               </td>
        </tr>
 
        </table>
index cdaf3f5ea9964c21b3b5c75e3c19805f47362d23..a3548fb252553bf633937b283d274ae5a7e6b576 100644 (file)
@@ -1,45 +1,55 @@
 ! Copyright (C) 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: kernel sequences accessors namespaces combinators words
-assocs db.tuples arrays splitting strings validators
+assocs db.tuples arrays splitting strings validators urls
 html.elements
 html.components
-html.templates.chloe
-http.server.boilerplate
-http.server.auth.providers
-http.server.auth.providers.db
-http.server.auth.login
-http.server.auth
-http.server.sessions
-http.server.actions
+furnace.boilerplate
+furnace.auth.providers
+furnace.auth.providers.db
+furnace.auth.login
+furnace.auth
+furnace.sessions
+furnace.actions
 http.server ;
 IN: webapps.user-admin
 
-: admin-template ( name -- template )
-    "resource:extra/webapps/user-admin/" swap ".xml" 3append <chloe> ;
+: word>string ( word -- string )
+    [ word-vocabulary ] [ drop ":" ] [ word-name ] tri 3append ;
 
 : words>strings ( seq -- seq' )
-    [ [ word-vocabulary ] [ drop ":" ] [ word-name ] tri 3append ] map ;
+    [ word>string ] map ;
+
+: string>word ( string -- word )
+    ":" split1 swap lookup ;
 
 : strings>words ( seq -- seq' )
-    [ ":" split1 swap lookup ] map ;
+    [ string>word ] map ;
 
 : <user-list-action> ( -- action )
     <page-action>
         [ f <user> select-tuples "users" set-value ] >>init
-        "user-list" admin-template >>template ;
+        "$user-admin/user-list" >>template ;
+
+: init-capabilities ( -- )
+    capabilities get words>strings "capabilities" set-value ;
+
+: selected-capabilities ( -- seq )
+    "capabilities" value
+    [ param empty? not ] filter
+    [ string>word ] map ;
 
 : <new-user-action> ( -- action )
     <page-action>
         [
-            "username" param <user> from-tuple
-            capabilities get words>strings "all-capabilities" set-value
+            "username" param <user> from-object
+            init-capabilities
         ] >>init
 
-        "new-user" admin-template >>template
+        "$user-admin/new-user" >>template
 
         [
-            capabilities get words>strings "all-capabilities" set-value
+            init-capabilities
 
             {
                 { "username" [ v-username ] }
@@ -62,10 +72,11 @@ IN: webapps.user-admin
                 "email" value >>email
                 "new-password" value >>encoded-password
                 H{ } clone >>profile
+                selected-capabilities >>capabilities
 
             insert-tuple
 
-            "$user-admin" f <standard-redirect>
+            URL" $user-admin" <redirect>
         ] >>submit ;
 
 : validate-username ( -- )
@@ -77,15 +88,16 @@ IN: webapps.user-admin
             validate-username
 
             "username" value <user> select-tuple
-            [ from-tuple ] [ capabilities>> words>strings "capabilities" set-value ] bi
+            [ from-object ]
+            [ capabilities>> [ "true" swap word>string set-value ] each ] bi
 
-            capabilities get words>strings "all-capabilities" set-value
+            capabilities get words>strings "capabilities" set-value
         ] >>init
 
-        "edit-user" admin-template >>template
+        "$user-admin/edit-user" >>template
 
         [
-            capabilities get words>strings "all-capabilities" set-value
+            init-capabilities
 
             {
                 { "username" [ v-username ] }
@@ -93,7 +105,6 @@ IN: webapps.user-admin
                 { "new-password" [ [ v-password ] v-optional ] }
                 { "verify-password" [ [ v-password ] v-optional ] }
                 { "email" [ [ v-email ] v-optional ] }
-                { "capabilities" [ ] }
             } validate-params
 
             "new-password" "verify-password"
@@ -106,19 +117,15 @@ IN: webapps.user-admin
             "username" value <user> select-tuple
                 "realname" value >>realname
                 "email" value >>email
+                selected-capabilities >>capabilities
 
             "new-password" value empty? [
                 "new-password" value >>encoded-password
             ] unless
 
-            "capabilities" value {
-                { [ dup string? ] [ 1array ] }
-                { [ dup array? ] [ ] }
-            } cond strings>words >>capabilities
-
             update-tuple
 
-            "$user-admin" f <standard-redirect>
+            URL" $user-admin" <redirect>
         ] >>submit ;
 
 : <delete-user-action> ( -- action )
@@ -130,7 +137,7 @@ IN: webapps.user-admin
             [ logout-all-sessions ]
             bi
 
-            "$user-admin" f <standard-redirect>
+            URL" $user-admin" <redirect>
         ] >>submit ;
 
 TUPLE: user-admin < dispatcher ;
@@ -146,7 +153,7 @@ can-administer-users? define-capability
         <edit-user-action> "edit" add-responder
         <delete-user-action> "delete" add-responder
     <boilerplate>
-        "user-admin" admin-template >>template
+        "$user-admin/user-admin" >>template
     { can-administer-users? } <protected> ;
 
 : make-admin ( username -- )
index 020d053e039853407b51579dbae7384b4b95bbc3..83b3f97cf9958281ead989686a8785d920769d62 100644 (file)
@@ -6,13 +6,13 @@
 
        <ul>
 
-               <t:each-tuple t:values="users">
+               <t:bind-each t:name="users">
                        <li>
                                <t:a t:href="$user-admin/edit" t:query="username">
                                        <t:label t:name="username" />
                                </t:a>
                        </li>
-               </t:each-tuple>
+               </t:bind-each>
 
        </ul>
 
index a552c2618f6a7d9cbdb988ca4bd2d7aed8ce7b3e..e19c531d3d383ecf052af6bfa9e6895ac2142bf1 100644 (file)
@@ -5,11 +5,11 @@
        <t:title>All Articles</t:title>
 
        <ul>
-               <t:each-tuple t:values="articles">
+               <t:bind-each t:name="articles">
                        <li>
                                <t:a t:href="view" t:query="title"><t:label t:name="title"/></t:a>
                        </li>
-               </t:each-tuple>
+               </t:bind-each>
        </ul>
 
 </t:chloe>
index 5efa0c045aea60e30bdba5e3723f30444c403959..95fb0de2feb89392132965d2db5234d0097036ea 100644 (file)
@@ -5,7 +5,7 @@
        <t:title>Recent Changes</t:title>
 
        <ul>
-               <t:each-tuple t:values="changes">
+               <t:bind-each t:name="changes">
                        <li>
                                <t:a t:href="title" t:query="title"><t:label t:name="title" /></t:a>
                                on
@@ -13,7 +13,7 @@
                                by
                                <t:a t:href="user-edits" t:query="author"><t:label t:name="author" /></t:a>
                        </li>
-               </t:each-tuple>
+               </t:bind-each>
        </ul>
 
 </t:chloe>
index 0fb0d6bae678c5a47942df66003dc45834749938..9feb12dc4394adb274bc579340e1fe45cc2368b3 100644 (file)
@@ -2,34 +2,34 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-       <t:bind-tuple t:name="old">
+       <t:bind t:name="old">
                <t:title>Diff: <t:label t:name="title" /></t:title>
-       </t:bind-tuple>
+       </t:bind>
 
        <table>
                <tr>
                        <th class="field-label">Old revision:</th>
-                       <t:bind-tuple t:name="old">
+                       <t:bind t:name="old">
                                <td>Created on <t:label t:name="date" /> by <t:a t:href="user-edits" t:query="author"><t:label t:name="author" /></t:a>.</td>
-                       </t:bind-tuple>
+                       </t:bind>
                </tr>
                <tr>
                        <th class="field-label">New revision:</th>
-                       <t:bind-tuple t:name="old">
+                       <t:bind t:name="old">
                                <td>Created on <t:label t:name="date" /> by <t:a t:href="user-edits" t:query="author"><t:label t:name="author" /></t:a>.</td>
-                       </t:bind-tuple>
+                       </t:bind>
                </tr>
        </table>
 
        <t:comparison t:name="diff" />
 
-       <t:bind-tuple t:name="old">
+       <t:bind t:name="old">
                <div class="navbar">
                        <t:a t:href="$wiki/view" t:query="title">Latest</t:a>
                        | <t:a t:href="$wiki/revisions" t:query="title">Revisions</t:a>
                        | <t:a t:href="$wiki/edit" t:query="title">Edit</t:a>
                        | <t:button t:action="$wiki/delete" t:for="title" class="link-button link">Delete</t:button>
                </div>
-       </t:bind-tuple>
+       </t:bind>
 
 </t:chloe>
index 4b7bdadf50251f11aa184486f9f0b0ca4eb80f76..0a0de8e470e2d8e945e71ac488fad715b028e4a5 100644 (file)
@@ -4,15 +4,23 @@
 
        <t:title>Revisions of <t:label t:name="title" /></t:title>
 
-       <ul>
-               <t:each-tuple t:values="revisions">
-                       <li>
-                               <t:a t:href="revision" t:query="id"><t:label t:name="date" /></t:a>
-                               by
-                               <t:a t:href="user-edits" t:query="author"><t:label t:name="author" /></t:a>
-                       </li>
-               </t:each-tuple>
-       </ul>
+       <div class="revisions">
+               <table>
+                       <tr>
+                               <th>Revision</th>
+                               <th>Author</th>
+                               <th>Rollback</th>
+                       </tr>
+
+                       <t:bind-each t:name="revisions">
+                               <tr>
+                                       <td> <t:a t:href="revision" t:query="id"><t:label t:name="date" /></t:a> </td>
+                                       <td> <t:a t:href="user-edits" t:query="author"><t:label t:name="author" /></t:a> </td>
+                                       <td> <t:button t:action="rollback" t:for="id" class="link link-button">Rollback</t:button> </td>
+                               </tr>
+                       </t:bind-each>
+               </table>
+       </div>
 
        <h2>View Differences</h2>
 
@@ -23,9 +31,9 @@
                                
                                <td>
                                        <select name="old-id">
-                                               <t:each-tuple t:values="revisions">
+                                               <t:bind-each t:name="revisions">
                                                        <option> <t:label t:name="id" /> </option>
-                                               </t:each-tuple>
+                                               </t:bind-each>
                                        </select>
                                </td>
                        </tr>
@@ -34,9 +42,9 @@
                                
                                <td>
                                        <select name="new-id">
-                                               <t:each-tuple t:values="revisions">
+                                               <t:bind-each t:name="revisions">
                                                        <option> <t:label t:name="id" /> </option>
-                                               </t:each-tuple>
+                                               </t:bind-each>
                                        </select>
                                </td>
                        </tr>
                <input type="submit" value="View" />
        </form>
 
+       <br/>
+
+       <div class="navbar">
+               <t:a t:href="$wiki/view" t:query="title">Latest</t:a>
+               | <t:a t:href="$wiki/revisions" t:query="title">Revisions</t:a>
+               | <t:a t:href="$wiki/edit" t:query="title">Edit</t:a>
+               | <t:button t:action="$wiki/delete" t:for="title" class="link-button link">Delete</t:button>
+       </div>
+
 </t:chloe>
index cf19a3837054227a6f78eda2d47913f27b686757..61809802d99bfa1af980e4a0648d688390360d67 100644 (file)
@@ -5,13 +5,13 @@
        <t:title>Edits by <t:label t:name="author" /></t:title>
 
        <ul>
-               <t:each-tuple t:values="user-edits">
+               <t:bind-each t:name="user-edits">
                        <li>
                                <t:a t:href="view" t:query="title"><t:label t:name="title" /></t:a>
                                on
                                <t:a t:href="revision" t:query="id"><t:label t:name="date" /></t:a>
                        </li>
-               </t:each-tuple>
+               </t:bind-each>
        </ul>
 
 </t:chloe>
index e737cdd898f395229a52bf08bd0ad91f3857b044..4825839ab3d49c402ca0f1da3d52e405faaf348b 100644 (file)
@@ -1,8 +1,3 @@
-.comparison table, {
-    border-color: #666;
-    border-style: solid;
-}
-
 .comparison th {
     border-width: 1px;
     border-color: #666;
 }
 
 .comparison table {
+    border-color: #666;
+    border-style: solid;
     border-width: 1px;
     border-spacing: 0;
     border-collapse: collapse;
 }
 
-
 .insert {
     background-color: #9f9;
 }
 .delete {
     background-color: #f99;
 }
+
+.revisions table, .revisions td, .revisions th {
+    border-color: #666;
+    border-style: solid;
+}
+
+.revisions table {
+    border-width: 0 0 1px 1px;
+    border-spacing: 0;
+    border-collapse: collapse;
+}
+
+.revisions td, .revisions th {
+    margin: 0;
+    padding: 4px;
+    border-width: 1px 1px 0 0;
+}
+
index 344a3d40bd9cd40d3ef7619513ee3632742c6e26..a1eb8bffc541c372007ce4975d0384b752fe4970 100644 (file)
@@ -3,14 +3,13 @@
 USING: accessors kernel hashtables calendar
 namespaces splitting sequences sorting math.order
 html.components
-html.templates.chloe
 http.server
-http.server.actions
-http.server.auth
-http.server.auth.login
-http.server.boilerplate
+furnace.actions
+furnace.auth
+furnace.auth.login
+furnace.boilerplate
 validators
-db.types db.tuples lcs farkup ;
+db.types db.tuples lcs farkup urls ;
 IN: webapps.wiki
 
 TUPLE: article title revision ;
@@ -41,18 +40,17 @@ revision "REVISIONS" {
 
 : init-revisions-table revision ensure-table ;
 
-: wiki-template ( name -- template )
-    "resource:extra/webapps/wiki/" swap ".xml" 3append <chloe> ;
-
-: <title-redirect> ( title next -- response )
-    swap "title" associate <standard-redirect> ;
-
 : validate-title ( -- )
     { { "title" [ v-one-line ] } } validate-params ;
 
 : <main-article-action> ( -- action )
     <action>
-        [ "Front Page" "$wiki/view" <title-redirect> ] >>display ;
+        [
+            <url>
+                "$wiki/view" >>path
+                "Front Page" "title" set-query-param
+            <redirect>
+        ] >>display ;
 
 : <view-article-action> ( -- action )
     <action>
@@ -65,10 +63,13 @@ revision "REVISIONS" {
 
         [
             "title" value dup <article> select-tuple [
-                revision>> <revision> select-tuple from-tuple
-                "view" wiki-template <html-content>
+                revision>> <revision> select-tuple from-object
+                "$wiki/view" <chloe-content>
             ] [
-                "$wiki/edit" <title-redirect>
+                <url>
+                    "$wiki/edit" >>path
+                    swap "title" set-query-param
+                <redirect>
             ] ?if
         ] >>display ;
 
@@ -77,10 +78,10 @@ revision "REVISIONS" {
         [
             { { "id" [ v-integer ] } } validate-params
             "id" value <revision>
-            select-tuple from-tuple
+            select-tuple from-object
         ] >>init
 
-        "view" wiki-template >>template ;
+        "$wiki/view" >>template ;
 
 : add-revision ( revision -- )
     [ insert-tuple ]
@@ -97,11 +98,11 @@ revision "REVISIONS" {
         [
             validate-title
             "title" value <article> select-tuple [
-                revision>> <revision> select-tuple from-tuple
+                revision>> <revision> select-tuple from-object
             ] when*
         ] >>init
 
-        "edit" wiki-template >>template
+        "$wiki/edit" >>template
         
         [
             validate-title
@@ -113,7 +114,12 @@ revision "REVISIONS" {
                 logged-in-user get username>> >>author
                 "content" value >>content
             [ add-revision ]
-            [ title>> "$wiki/view" <title-redirect> ] bi
+            [
+                <url>
+                    "$wiki/view" >>path
+                    swap title>> "title" set-query-param
+                <redirect>
+            ] bi
         ] >>submit ;
 
 : <list-revisions-action> ( -- action )
@@ -125,7 +131,24 @@ revision "REVISIONS" {
             "revisions" set-value
         ] >>init
 
-        "revisions" wiki-template >>template ;
+        "$wiki/revisions" >>template ;
+
+: <rollback-action> ( -- action )
+    <action>
+        [
+            { { "id" [ v-integer ] } } validate-params
+        ] >>validate
+        
+        [
+            "id" value <revision> select-tuple clone f >>id
+            [ add-revision ]
+            [
+                <url>
+                    "$wiki/view" >>path
+                    swap title>> "title" set-query-param
+                <redirect>
+            ] bi
+        ] >>submit ;
 
 : <list-changes-action> ( -- action )
     <page-action>
@@ -135,7 +158,7 @@ revision "REVISIONS" {
             "changes" set-value
         ] >>init
 
-        "changes" wiki-template >>template ;
+        "$wiki/changes" >>template ;
 
 : <delete-action> ( -- action )
     <action>
@@ -144,7 +167,7 @@ revision "REVISIONS" {
         [
             "title" value <article> delete-tuples
             f <revision> "title" value >>title delete-tuples
-            "" f <standard-redirect>
+            URL" $wiki" <redirect>
         ] >>submit ;
 
 : <diff-action> ( -- action )
@@ -162,7 +185,7 @@ revision "REVISIONS" {
             2bi
         ] >>init
 
-        "diff" wiki-template >>template ;
+        "$wiki/diff" >>template ;
 
 : <list-articles-action> ( -- action )
     <page-action>
@@ -172,7 +195,7 @@ revision "REVISIONS" {
             "articles" set-value
         ] >>init
 
-        "articles" wiki-template >>template ;
+        "$wiki/articles" >>template ;
 
 : <user-edits-action> ( -- action )
     <page-action>
@@ -182,7 +205,7 @@ revision "REVISIONS" {
             select-tuples "user-edits" set-value
         ] >>init
 
-        "user-edits" wiki-template >>template ;
+        "$wiki/user-edits" >>template ;
 
 TUPLE: wiki < dispatcher ;
 
@@ -192,6 +215,7 @@ TUPLE: wiki < dispatcher ;
         <view-article-action> "view" add-responder
         <view-revision-action> "revision" add-responder
         <list-revisions-action> "revisions" add-responder
+        <rollback-action> "rollback" add-responder
         <user-edits-action> "user-edits" add-responder
         <diff-action> "diff" add-responder
         <list-articles-action> "articles" add-responder
@@ -199,4 +223,4 @@ TUPLE: wiki < dispatcher ;
         <edit-article-action> { } <protected> "edit" add-responder
         <delete-action> { } <protected> "delete" add-responder
     <boilerplate>
-        "wiki-common" wiki-template >>template ;
+        "$wiki/wiki-common" >>template ;
index 6eccddc94af049d2d5304f5e4d187cd83fec3851..9167517bb2ed35a3a1c75dfccf3c855f187b7351 100755 (executable)
@@ -1,14 +1,14 @@
 USING: xmode.tokens xmode.marker xmode.catalog kernel
 html.elements io io.files sequences words io.encodings.utf8
-namespaces xml.entities ;
+namespaces xml.entities accessors ;
 IN: xmode.code2html
 
 : htmlize-tokens ( tokens -- )
     [
-        dup token-str swap token-id [
+        [ str>> ] [ id>> ] bi [
             <span word-name =class span> escape-string write </span>
         ] [
-            write
+            escape-string write
         ] if*
     ] each ;
 
index 2f56a5b8194a13d7a4f50213dea39fee08a18a6e..67cb60f8a0d53def24381d8e3210ce78df3d363c 100755 (executable)
@@ -12,5 +12,5 @@ IN: xmode.code2html.responder
             , utf8 [\r
                 , file-name input-stream get htmlize-stream\r
             ] with-file-reader\r
-        ] <html-content>\r
+        ] "text/html" <content>\r
     ] <file-responder> ;\r