} case ;\r
\r
M: action modify-form\r
- drop request get url>> revalidate-url-key hidden-form-field ;\r
+ drop url get revalidate-url-key hidden-form-field ;\r
\r
: check-validation ( -- )\r
validation-failed? [ validation-failed ] when ;\r
! 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 html.templates.chloe.syntax db.types db.tuples
-http http.server http.server.filters
-furnace furnace.cache furnace.sessions furnace.redirection ;
+assocs hashtables math.parser urls combinators
+logging db.types db.tuples
+html.elements
+html.templates.chloe.syntax
+http
+http.server
+http.server.filters
+furnace
+furnace.cache
+furnace.sessions
+furnace.redirection ;
IN: furnace.asides
TUPLE: aside < server-state session method url post-data ;
url>> path>> split-path
asides get responder>> call-responder ;
+\ end-aside-post DEBUG add-input-logging
+
ERROR: end-aside-in-get-error ;
: get-aside ( id -- aside )
! Copyright (c) 2008 Slava Pestov\r
! See http://factorcode.org/license.txt for BSD license.\r
USING: accessors assocs namespaces kernel sequences sets\r
-destructors combinators fry\r
+destructors combinators fry logging\r
io.encodings.utf8 io.encodings.string io.binary random\r
checksums checksums.sha2\r
html.forms\r
\r
SYMBOL: logged-in-user\r
\r
-: logged-in? ( -- ? ) logged-in-user get >boolean ;\r
+: logged-in? ( -- ? )\r
+ logged-in-user get >boolean ;\r
+\r
+: username ( -- string/f )\r
+ logged-in-user get dup [ username>> ] when ;\r
\r
GENERIC: init-user-profile ( responder -- )\r
\r
M: filter-responder init-user-profile\r
responder>> init-user-profile ;\r
\r
-: have-capability? ( capability -- ? )\r
- logged-in-user get capabilities>> member? ;\r
-\r
: profile ( -- assoc ) logged-in-user get profile>> ;\r
\r
: user-changed ( -- )\r
\r
GENERIC: login-required* ( realm -- response )\r
\r
+GENERIC: init-realm ( realm -- )\r
+\r
GENERIC: logged-in-username ( realm -- username )\r
\r
: login-required ( -- * ) realm get login-required* exit-with ;\r
: init-user ( user -- )\r
[ [ logged-in-user set ] [ save-user-after ] bi ] when* ;\r
\r
+\ init-user DEBUG add-input-logging\r
+\r
M: realm call-responder* ( path responder -- response )\r
dup realm set\r
- dup logged-in-username dup [ users get-user ] when init-user\r
+ logged-in? [\r
+ dup init-realm\r
+ dup logged-in-username\r
+ dup [ users get-user ] when\r
+ init-user\r
+ ] unless\r
call-next-method ;\r
\r
: encode-password ( string salt -- bytes )\r
protected new\r
swap >>responder ;\r
\r
-: check-capabilities ( responder user/f -- ? )\r
- {\r
+: have-capabilities? ( capabilities -- ? )\r
+ logged-in-user get {\r
{ [ dup not ] [ 2drop f ] }\r
{ [ dup deleted>> 1 = ] [ 2drop f ] }\r
- [ [ capabilities>> ] bi@ subset? ]\r
+ [ capabilities>> subset? ]\r
} cond ;\r
\r
M: protected call-responder* ( path responder -- response )\r
'[\r
, ,\r
dup protected set\r
- dup logged-in-user get check-capabilities\r
+ dup capabilities>> have-capabilities?\r
[ call-next-method ] [ 2drop realm get login-required* ] if\r
] if-secure-realm ;\r
\r
{ realm "features/edit-profile/edit-profile" } >>template
[
- logged-in-user get username>> "username" set-value
+ username "username" set-value
{
{ "realname" [ [ v-one-line ] v-optional ] }
{ "password" "new-password" "verify-password" }
[ value empty? not ] contains? [
- "password" value logged-in-user get username>> check-login
+ "password" value username check-login
[ "incorrect password" validation-error ] unless
same-password-twice
SYMBOL: lost-password-from
: current-host ( -- string )
- request get url>> host>> host-name or ;
+ url get host>> host-name or ;
: new-password-url ( user -- url )
URL" recover-3" clone
! Copyright (c) 2008 Slava Pestov\r
! See http://factorcode.org/license.txt for BSD license.\r
USING: kernel accessors namespaces sequences math.parser\r
-calendar validators urls html.forms\r
+calendar validators urls logging html.forms\r
http http.server http.server.dispatchers\r
furnace\r
furnace.auth\r
\r
TUPLE: login-realm < realm timeout domain ;\r
\r
-M: login-realm call-responder*\r
- [ name>> client-permit-id permit-id set ]\r
- [ call-next-method ]\r
- bi ;\r
+M: login-realm init-realm\r
+ name>> client-permit-id permit-id set ;\r
\r
M: login-realm logged-in-username\r
drop permit-id get dup [ get-permit-uid ] when ;\r
: put-permit-cookie ( response -- response' )\r
<permit-cookie> put-cookie ;\r
\r
+\ put-permit-cookie DEBUG add-input-logging\r
+\r
: successful-login ( user -- response )\r
[ username>> make-permit permit-id set ] [ init-user ] bi\r
URL" $realm" end-aside\r
put-permit-cookie ;\r
\r
+\ successful-login DEBUG add-input-logging\r
+\r
: logout ( -- )\r
permit-id get [ delete-permit ] when*\r
URL" $realm" end-aside ;\r
-USING: accessors namespaces combinators.lib kernel
-db.tuples db.types
-furnace.auth furnace.sessions furnace.cache
-combinators.short-circuit ;
+USING: accessors namespaces kernel combinators.short-circuit
+db.tuples db.types furnace.auth furnace.sessions furnace.cache ;
IN: furnace.auth.login.permits
! Copyright (c) 2008 Slava Pestov
! See http://factorcode.org/license.txt for BSD license.
-USING: accessors kernel math.order namespaces combinators.lib
+USING: accessors kernel math.order namespaces furnace combinators.short-circuit
html.forms
html.templates
html.templates.chloe
locals
http.server
-http.server.filters
-furnace combinators.short-circuit ;
+http.server.filters ;
IN: furnace.boilerplate
TUPLE: boilerplate < filter-responder template init ;
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
-USING: namespaces assocs assocs.lib kernel sequences accessors
+USING: namespaces assocs kernel sequences accessors
urls db.types db.tuples math.parser fry
http http.server http.server.filters http.server.redirection
furnace furnace.cache furnace.sessions furnace.redirection ;
"user-agent" request get header>> at "" or ;
: same-host? ( url -- ? )
- request get url>>
+ url get
[ [ protocol>> ] [ host>> ] [ port>> ] tri 3array ] bi@ = ;
: cookie-client-state ( key request -- value/f )
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: kernel accessors combinators namespaces fry
-io.servers.connection
+io.servers.connection urls
http http.server http.server.redirection http.server.filters
furnace ;
IN: furnace.redirection
C: <secure-only> secure-only
: if-secure ( quot -- )
- >r request get url>> protocol>> "http" =
- [ request get url>> <secure-redirect> ]
+ >r url get protocol>> "http" =
+ [ url get <secure-redirect> ]
r> if ; inline
M: secure-only call-responder*
! See http://factorcode.org/license.txt for BSD license.
USING: assocs kernel math.intervals math.parser namespaces
strings random accessors quotations hashtables sequences continuations
-fry calendar combinators combinators.lib destructors alarms
+fry calendar combinators combinators.short-circuit destructors alarms
io.servers.connection
db db.tuples db.types
http http.server http.server.dispatchers http.server.filters
html.elements
-furnace furnace.cache combinators.short-circuit ;
+furnace furnace.cache ;
IN: furnace.sessions
TUPLE: session < server-state namespace user-agent client changed? ;
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
-USING: accessors kernel sequences fry sequences.lib
+USING: accessors kernel sequences fry
combinators syndication
http.server.responses http.server.redirection
furnace furnace.actions ;
]
} case ;
+: check-cookie-value ( string -- string )
+ [ "Cookie value must not be f" throw ] unless* ;
+
: (unparse-cookie) ( cookie -- strings )
[
dup name>> check-cookie-string >lower
- over value>> unparse-cookie-value
+ over value>> check-cookie-value unparse-cookie-value
"$path" over path>> unparse-cookie-value
"$domain" over domain>> unparse-cookie-value
drop
: unparse-set-cookie ( cookie -- string )
[
dup name>> check-cookie-string >lower
- over value>> unparse-cookie-value
+ over value>> check-cookie-value unparse-cookie-value
"path" over path>> unparse-cookie-value
"domain" over domain>> unparse-cookie-value
"expires" over expires>> unparse-cookie-value
-USING: combinators.short-circuit math math.order math.parser kernel combinators.lib
+USING: combinators.short-circuit math math.order math.parser kernel
sequences sequences.deep peg peg.parsers assocs arrays
hashtables strings unicode.case namespaces ascii ;
IN: http.parsers
\r
[ "PATH_TRANSLATED" set ] [ "SCRIPT_FILENAME" set ] bi\r
\r
- request get url>> path>> "SCRIPT_NAME" set\r
+ url get path>> "SCRIPT_NAME" set\r
\r
- request get url>> host>> "SERVER_NAME" set\r
- request get url>> port>> number>string "SERVER_PORT" set\r
+ url get host>> "SERVER_NAME" set\r
+ url get port>> number>string "SERVER_PORT" set\r
"" "PATH_INFO" set\r
"" "REMOTE_HOST" set\r
"" "REMOTE_ADDR" set\r
"" "REMOTE_IDENT" set\r
\r
request get method>> "REQUEST_METHOD" set\r
- request get url>> query>> assoc>query "QUERY_STRING" set\r
+ url get query>> assoc>query "QUERY_STRING" set\r
request get "cookie" header "HTTP_COOKIE" set \r
\r
request get "user-agent" header "HTTP_USER_AGENT" set\r
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: kernel namespaces sequences assocs accessors splitting
-unicode.case http http.server http.server.responses ;
+unicode.case urls http http.server http.server.responses ;
IN: http.server.dispatchers
TUPLE: dispatcher default responders ;
>lower "www." ?head drop "." ?tail drop ;
: find-vhost ( dispatcher -- responder )
- request get url>> host>> canonical-host over responders>> at*
+ url get host>> canonical-host over responders>> at*
[ nip ] [ drop default>> ] if ;
M: vhost-dispatcher call-responder* ( path dispatcher -- response )
M: string relative-to-request ;
M: url relative-to-request
- request get url>>
+ url get
clone
f >>query
swap derive-url ensure-port ;
: ensure-domain ( cookie -- cookie )
[
- request get url>>
- host>> dup "localhost" =
+ url get host>> dup "localhost" =
[ drop ] [ or ] if
] change-domain ;
"/" split harvest ;
: init-request ( request -- )
- request set
+ [ request set ] [ url>> url set ] bi
V{ } clone responder-nesting set ;
: dispatch-request ( request -- response )
: ?benchmark ( quot -- )
benchmark? get [
- [ benchmark ] [ first ] bi request get url>> rot 3array
+ [ benchmark ] [ first ] bi url get rot 3array
httpd-benchmark
] [ call ] if ; inline
[
64 1024 * limit-input
?refresh-all
- read-request
+ [ read-request ] ?benchmark
[ do-request ] ?benchmark
[ do-response ] ?benchmark
] with-destructors ;
"index.html" append-path dup exists? [ drop f ] unless ;\r
\r
: serve-directory ( filename -- response )\r
- request get url>> path>> "/" tail? [\r
+ url get path>> "/" tail? [\r
dup\r
find-index [ serve-file ] [ list-directory ] ?if\r
] [\r
drop\r
- request get url>> clone [ "/" append ] change-path <permanent-redirect>\r
+ url get clone [ "/" append ] change-path <permanent-redirect>\r
] if ;\r
\r
: serve-object ( filename -- response )\r
[
validate-post
- logged-in-user get username>> "author" set-value
+ username "author" set-value
] >>validate
[
f <post>
dup { "title" "content" } to-object
- logged-in-user get username>> >>author
+ username >>author
now >>date
[ insert-tuple ] [ entity-url <redirect> ] bi
] >>submit
"make a new blog post" >>description ;
: authorize-author ( author -- )
- logged-in-user get username>> =
- can-administer-blogs? have-capability? or
+ username =
+ { can-administer-blogs? } have-capabilities? or
[ login-required ] unless ;
: do-post-action ( -- )
[
validate-comment
- logged-in-user get username>> "author" set-value
+ username "author" set-value
] >>validate
[
"parent" value f <comment>
"content" value >>content
- logged-in-user get username>> >>author
+ username >>author
now >>date
[ insert-tuple ] [ entity-url <redirect> ] bi
] >>submit
: <todo> ( id -- todo )
todo new
swap >>id
- logged-in-user get username>> >>uid ;
+ username >>uid ;
: <view-action> ( -- action )
<page-action>
<t:title>Recent Changes</t:title>
- <div class="revisions">
-
- <table>
-
- <tr>
- <th>Article</th>
- <th>Date</th>
- <th>By</th>
- </tr>
-
- <t:bind-each t:name="changes">
- <tr>
- <td><t:a t:href="$wiki/view" t:rest="title"><t:label t:name="title" /></t:a></td>
- <td><t:a t:href="$wiki/revision" t:rest="id"><t:label t:name="date" /></t:a></td>
- <td><t:a t:href="$wiki/user-edits" t:rest="author"><t:label t:name="author" /></t:a></td>
- </tr>
- </t:bind-each>
-
- </table>
-
- </div>
-
</t:chloe>
</tr>
<tr>
<th class="field-label">New revision:</th>
- <t:bind t:name="old">
+ <t:bind t:name="new">
<td>Created on <t:label t:name="date" /> by <t:a t:href="user-edits" t:rest="author"><t:label t:name="author" /></t:a>.</td>
</t:bind>
</tr>
<t:title>Edit: <t:label t:name="title" /></t:title>
- <t:form t:action="$wiki/edit" t:for="title">
+ <t:form t:action="$wiki/submit" t:for="title">
<p>
<t:textarea t:name="content" t:rows="30" t:cols="80" />
</p>
+ <p>
+ Describe this revision:
+ <t:field t:name="description" t:size="60" />
+ </p>
+
<p>
<input type="submit" value="Save" />
</p>
--- /dev/null
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+ <div class="revisions">
+
+ <table>
+
+ <tr>
+ <th>Article</th>
+ <th>Date</th>
+ <th>By</th>
+ <th>Description</th>
+ <th>Rollback</th>
+ </tr>
+
+ <t:bind-each t:name="revisions">
+ <tr>
+ <td> <t:a t:href="$wiki/view" t:rest="title"><t:label t:name="title" /></t:a> </td>
+ <td> <t:a t:href="$wiki/revision" t:rest="id"><t:label t:name="date" /></t:a> </td>
+ <td> <t:a t:href="$wiki/user-edits" t:rest="author"><t:label t:name="author" /> </t:a></td>
+ <td> <t:label t:name="description" /> </td>
+ <td> <t:button class="link link-button" t:action="$wiki/rollback" t:for="id">Rollback</t:button> </td>
+ </tr>
+ </t:bind-each>
+
+ </table>
+
+ </div>
+
+ <t:call-next-template />
+
+</t:chloe>
<t:title>Revisions of <t:label t:name="title" /></t:title>
- <div class="revisions">
- <table>
- <tr>
- <th>Revision</th>
- <th>By</th>
- <th>Rollback</th>
- </tr>
-
- <t:bind-each t:name="revisions">
- <tr>
- <td> <t:a t:href="$wiki/revision" t:rest="id"><t:label t:name="date" /></t:a> </td>
- <td> <t:a t:href="$wiki/user-edits" t:rest="author"><t:label t:name="author" /></t:a> </td>
- <td> <t:button t:action="$wiki/rollback" t:for="id" class="link link-button">Rollback</t:button> </td>
- </tr>
- </t:bind-each>
- </table>
- </div>
-
<h2>View Differences</h2>
<t:form t:action="$wiki/diff" t:method="get">
<t:title>Edits by <t:label t:name="author" /></t:title>
- <ul>
- <t:bind-each t:name="user-edits">
- <li>
- <t:a t:href="$wiki/view" t:rest="title"><t:label t:name="title" /></t:a>
- on
- <t:a t:href="$wiki/revision" t:rest="id"><t:label t:name="date" /></t:a>
- </li>
- </t:bind-each>
- </ul>
-
</t:chloe>
<t:farkup t:name="content" />
</div>
- <p><em>This revision created on <t:label t:name="date" /> by <t:a t:href="$wiki/user-edits" t:rest="author"><t:label t:name="author" /></t:a>.</em></p>
+ <p>
+ <em>This revision created on <t:label t:name="date" /> by <t:a t:href="$wiki/user-edits" t:rest="author"><t:label t:name="author" /></t:a>
+ <t:if t:value="description">
+ (<t:label t:name="description" />)
+ </t:if>
+ </em>
+ </p>
</t:chloe>
</t:if>
</tr>
- <tr>
- <td>
- <t:bind t:name="footer">
- <small>
- <t:farkup t:name="content" />
- </small>
- </t:bind>
- </td>
- </tr>
+ <t:if t:value="footer">
+ <tr>
+ <td>
+ <t:bind t:name="footer">
+ <small>
+ <t:farkup t:name="content" />
+ </small>
+ </t:bind>
+ </td>
+ </tr>
+ </t:if>
</table>
</t:chloe>
: <article> ( title -- article ) article new swap >>title ;
-TUPLE: revision id title author date content ;
+TUPLE: revision id title author date content description ;
revision "REVISIONS" {
{ "id" "ID" INTEGER +db-assigned-id+ }
{ "author" "AUTHOR" { VARCHAR 256 } +not-null+ } ! uid
{ "date" "DATE" TIMESTAMP +not-null+ }
{ "content" "CONTENT" TEXT +not-null+ }
+ { "description" "DESCRIPTION" TEXT }
} define-persistent
M: revision feed-entry-title
: validate-author ( -- )
{ { "author" [ v-username ] } } validate-params ;
+: <article-boilerplate> ( responder -- responder' )
+ <boilerplate>
+ { wiki "page-common" } >>template ;
+
: <main-article-action> ( -- action )
<action>
[ "Front Page" view-url <redirect> ] >>display ;
] [
edit-url <redirect>
] ?if
- ] >>display ;
+ ] >>display
+
+ <article-boilerplate> ;
: <view-revision-action> ( -- action )
<page-action>
URL" $wiki/view/" adjust-url present relative-link-prefix set
] >>init
- { wiki "view" } >>template ;
+ { wiki "view" } >>template
+
+ <article-boilerplate> ;
: <random-article-action> ( -- action )
<action>
[
validate-title
- "title" value <article> select-tuple [
- revision>> <revision> select-tuple from-object
- ] when*
+
+ "title" value <article> select-tuple
+ [ revision>> <revision> select-tuple ]
+ [ f <revision> "title" value >>title ]
+ if*
+
+ [ title>> "title" set-value ]
+ [ content>> "content" set-value ]
+ bi
] >>init
{ wiki "edit" } >>template
+ <article-boilerplate> ;
+
+: <submit-article-action> ( -- action )
+ <action>
[
validate-title
- { { "content" [ v-required ] } } validate-params
+
+ {
+ { "content" [ v-required ] }
+ { "description" [ [ v-one-line ] v-optional ] }
+ } validate-params
f <revision>
"title" value >>title
now >>date
- logged-in-user get username>> >>author
+ username >>author
"content" value >>content
+ "description" value >>description
[ add-revision ] [ title>> view-url <redirect> ] bi
] >>submit
<protected>
"edit wiki articles" >>description ;
+: <revisions-boilerplate> ( responder -- responder )
+ <boilerplate>
+ { wiki "revisions-common" } >>template ;
+
: list-revisions ( -- seq )
f <revision> "title" value >>title select-tuples
reverse-chronological-order ;
list-revisions "revisions" set-value
] >>init
- { wiki "revisions" } >>template ;
+ { wiki "revisions" } >>template
+
+ <revisions-boilerplate>
+ <article-boilerplate> ;
: <list-revisions-feed-action> ( -- action )
<feed-action>
[ list-revisions ] >>entries ;
+: rollback-description ( description -- description' )
+ [ "Rollback of '" swap "'" 3append ] [ "Rollback" ] if* ;
+
: <rollback-action> ( -- action )
<action>
[ validate-integer-id ] >>validate
[
- "id" value <revision> select-tuple clone f >>id
- [ add-revision ] [ title>> view-url <redirect> ] bi
- ] >>submit ;
+ "id" value <revision> select-tuple
+ f >>id
+ now >>date
+ username >>author
+ [ rollback-description ] change-description
+ [ add-revision ]
+ [ title>> revisions-url <redirect> ] bi
+ ] >>submit
+
+ <protected>
+ "rollback wiki articles" >>description ;
: list-changes ( -- seq )
f <revision> select-tuples
: <list-changes-action> ( -- action )
<page-action>
- [ list-changes "changes" set-value ] >>init
- { wiki "changes" } >>template ;
+ [ list-changes "revisions" set-value ] >>init
+ { wiki "changes" } >>template
+
+ <revisions-boilerplate> ;
: <list-changes-feed-action> ( -- action )
<feed-action>
: <diff-action> ( -- action )
<page-action>
+
[
{
{ "old-id" [ v-integer ] }
"old-id" "new-id"
[ value <revision> select-tuple ] bi@
[
- [ [ title>> "title" set-value ] [ "old" [ from-object ] nest-form ] bi ]
- [ "new" [ from-object ] nest-form ] bi*
+ over title>> "title" set-value
+ [ "old" [ from-object ] nest-form ]
+ [ "new" [ from-object ] nest-form ]
+ bi*
]
[ [ content>> string-lines ] bi@ diff "diff" set-value ]
2bi
] >>init
- { wiki "diff" } >>template ;
+ { wiki "diff" } >>template
+
+ <article-boilerplate> ;
: <list-articles-action> ( -- action )
<page-action>
[
validate-author
- list-user-edits "user-edits" set-value
+ list-user-edits "revisions" set-value
] >>init
- { wiki "user-edits" } >>template ;
+ { wiki "user-edits" } >>template
+
+ <revisions-boilerplate> ;
: <user-edits-feed-action> ( -- action )
<feed-action>
[ "author" value user-edits-url ] >>url
[ list-user-edits ] >>entries ;
-: <article-boilerplate> ( responder -- responder' )
- <boilerplate>
- { wiki "page-common" } >>template ;
-
: init-sidebar ( -- )
"Sidebar" latest-revision [ "sidebar" [ from-object ] nest-form ] when*
"Footer" latest-revision [ "footer" [ from-object ] nest-form ] when* ;
: <wiki> ( -- dispatcher )
wiki new-dispatcher
- <main-article-action> <article-boilerplate> "" add-responder
- <view-article-action> <article-boilerplate> "view" add-responder
- <view-revision-action> <article-boilerplate> "revision" add-responder
+ <main-article-action> "" add-responder
+ <view-article-action> "view" add-responder
+ <view-revision-action> "revision" add-responder
<random-article-action> "random" add-responder
- <list-revisions-action> <article-boilerplate> "revisions" add-responder
+ <list-revisions-action> "revisions" add-responder
<list-revisions-feed-action> "revisions.atom" add-responder
- <diff-action> <article-boilerplate> "diff" add-responder
- <edit-article-action> <article-boilerplate> "edit" add-responder
+ <diff-action> "diff" add-responder
+ <edit-article-action> "edit" add-responder
+ <submit-article-action> "submit" add-responder
<rollback-action> "rollback" add-responder
<user-edits-action> "user-edits" add-responder
<list-articles-action> "articles" add-responder