Implement flash scopes, improved validation and login page, improved http-post

db4
Slava Pestov 2008-06-04 19:54:05 -05:00
parent ab5843d831
commit 9861146d8d
34 changed files with 486 additions and 254 deletions

View File

@ -2,13 +2,15 @@
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: accessors sequences kernel assocs combinators USING: accessors sequences kernel assocs combinators
validators http hashtables namespaces fry continuations locals validators http hashtables namespaces fry continuations locals
io arrays math boxes io arrays math boxes splitting urls
xml.entities xml.entities
http.server http.server
http.server.responses http.server.responses
furnace furnace
furnace.flash
html.elements html.elements
html.components html.components
html.components
html.templates.chloe html.templates.chloe
html.templates.chloe.syntax ; html.templates.chloe.syntax ;
IN: furnace.actions IN: furnace.actions
@ -39,48 +41,68 @@ TUPLE: action rest-param init display validate submit ;
: <action> ( -- action ) : <action> ( -- action )
action new-action ; action new-action ;
: flashed-variables ( -- seq )
{ validation-messages named-validation-messages } ;
: handle-get ( action -- response ) : handle-get ( action -- response )
blank-values '[
[ init>> call ] ,
[ display>> call ] [ init>> call ]
bi ; [ drop flashed-variables restore-flash ]
[ display>> call ]
tri
] with-exit-continuation ;
: validation-failed ( -- * ) : validation-failed ( -- * )
request get method>> "POST" = request get method>> "POST" = [ f ] [ <400> ] if exit-with ;
[ action get display>> call ] [ <400> ] if exit-with ;
: handle-post ( action -- response ) : (handle-post) ( action -- response )
init-validation [ validate>> call ] [ submit>> call ] bi ;
blank-values
[ validate>> call ]
[ submit>> call ] bi ;
: handle-rest-param ( arg -- )
dup length 1 > action get rest-param>> not or
[ <404> exit-with ] [
action get rest-param>> associate rest-param set
] if ;
M: action call-responder* ( path action -- response )
dup action set
'[
, dup empty? [ drop ] [ handle-rest-param ] if
init-validation
,
request get
[ request-params rest-param get assoc-union params set ]
[ method>> ] bi
{
{ "GET" [ handle-get ] }
{ "HEAD" [ handle-get ] }
{ "POST" [ handle-post ] }
} case
] with-exit-continuation ;
: param ( name -- value ) : param ( name -- value )
params get at ; params get at ;
: revalidate-url-key "__u" ;
: check-url ( url -- ? )
request get url>>
[ [ protocol>> ] [ host>> ] [ port>> ] tri 3array ] bi@ = ;
: revalidate-url ( -- url/f )
revalidate-url-key param dup [ >url dup check-url swap and ] when ;
: handle-post ( action -- response )
'[
form-nesting-key params get at " " split
[ , (handle-post) ]
[ swap '[ , , nest-values ] ] reduce
call
] with-exit-continuation
[
revalidate-url
[ flashed-variables <flash-redirect> ] [ <403> ] if*
] unless* ;
: handle-rest-param ( path action -- assoc )
rest-param>> dup [ associate ] [ 2drop f ] if ;
: init-action ( path action -- )
blank-values
init-validation
handle-rest-param
request get request-params assoc-union params set ;
M: action call-responder* ( path action -- response )
[ init-action ] keep
request get method>> {
{ "GET" [ handle-get ] }
{ "HEAD" [ handle-get ] }
{ "POST" [ handle-post ] }
} case ;
M: action modify-form
drop request get url>> revalidate-url-key hidden-form-field ;
: check-validation ( -- ) : check-validation ( -- )
validation-failed? [ validation-failed ] when ; validation-failed? [ validation-failed ] when ;

View File

@ -0,0 +1,73 @@
! 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
furnace http http.server http.server.filters furnace.sessions
html.elements html.templates.chloe.syntax ;
IN: furnace.asides
TUPLE: asides < filter-responder ;
C: <asides> asides
: begin-aside* ( -- id )
request get
[ url>> ] [ post-data>> ] [ method>> ] tri 3array
asides sget set-at-unique
session-changed ;
: end-aside-post ( url post-data -- response )
request [
clone
swap >>post-data
swap >>url
] change
request get url>> path>> split-path
asides get responder>> call-responder ;
ERROR: end-aside-in-get-error ;
: end-aside* ( url id -- response )
request get method>> "POST" = [ end-aside-in-get-error ] unless
asides sget at [
first3 {
{ "GET" [ drop <redirect> ] }
{ "HEAD" [ drop <redirect> ] }
{ "POST" [ end-aside-post ] }
} case
] [ <redirect> ] ?if ;
SYMBOL: aside-id
: aside-id-key "__a" ;
: begin-aside ( -- )
begin-aside* aside-id set ;
: end-aside ( default -- response )
aside-id [ f ] change end-aside* ;
M: asides call-responder*
dup asides set
aside-id-key request get request-params at aside-id set
call-next-method ;
M: asides init-session*
H{ } clone asides sset
call-next-method ;
M: asides link-attr ( tag -- )
drop
"aside" optional-attr {
{ "none" [ aside-id off ] }
{ "begin" [ begin-aside ] }
{ "current" [ ] }
{ f [ ] }
} case ;
M: asides modify-query ( query responder -- query' )
drop
aside-id get [ aside-id-key associate assoc-union ] when* ;
M: asides modify-form ( responder -- )
drop aside-id get aside-id-key hidden-form-field ;

View File

@ -2,7 +2,7 @@
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: accessors quotations assocs kernel splitting USING: accessors quotations assocs kernel splitting
combinators sequences namespaces hashtables sets combinators sequences namespaces hashtables sets
fry arrays threads qualified random validators fry arrays threads qualified random validators words
io io
io.sockets io.sockets
io.encodings.utf8 io.encodings.utf8
@ -26,14 +26,29 @@ furnace.auth
furnace.auth.providers furnace.auth.providers
furnace.auth.providers.db furnace.auth.providers.db
furnace.actions furnace.actions
furnace.flows furnace.asides
furnace.flash
furnace.sessions furnace.sessions
furnace.boilerplate ; furnace.boilerplate ;
QUALIFIED: smtp QUALIFIED: smtp
IN: furnace.auth.login IN: furnace.auth.login
: word>string ( word -- string )
[ word-vocabulary ] [ drop ":" ] [ word-name ] tri 3append ;
: words>strings ( seq -- seq' )
[ word>string ] map ;
: string>word ( string -- word )
":" split1 swap lookup ;
: strings>words ( seq -- seq' )
[ string>word ] map ;
TUPLE: login < dispatcher users checksum ; TUPLE: login < dispatcher users checksum ;
TUPLE: protected < filter-responder description capabilities ;
: users ( -- provider ) : users ( -- provider )
login get users>> ; login get users>> ;
@ -64,7 +79,7 @@ M: user-saver dispose
! ! ! Login ! ! ! Login
: successful-login ( user -- response ) : successful-login ( user -- response )
username>> set-uid URL" $login" end-flow ; username>> set-uid URL" $login" end-aside ;
: login-failed ( -- * ) : login-failed ( -- * )
"invalid username or password" validation-error "invalid username or password" validation-error
@ -72,6 +87,13 @@ M: user-saver dispose
: <login-action> ( -- action ) : <login-action> ( -- action )
<page-action> <page-action>
[
protected fget [
[ description>> "description" set-value ]
[ capabilities>> words>strings "capabilities" set-value ] bi
] when*
] >>init
{ login "login" } >>template { login "login" } >>template
[ [
@ -177,7 +199,7 @@ M: user-saver dispose
drop drop
URL" $login" end-flow URL" $login" end-aside
] >>submit ; ] >>submit ;
! ! ! Password recovery ! ! ! Password recovery
@ -290,23 +312,23 @@ SYMBOL: lost-password-from
<action> <action>
[ [
f set-uid f set-uid
URL" $login" end-flow URL" $login" end-aside
] >>submit ; ] >>submit ;
! ! ! Authentication logic ! ! ! Authentication logic
: <protected> ( responder -- protected )
TUPLE: protected < filter-responder capabilities ; protected new
swap >>responder ;
C: <protected> protected
: show-login-page ( -- response ) : show-login-page ( -- response )
begin-flow begin-aside
URL" $login/login" <redirect> ; URL" $login/login" { protected } <flash-redirect> ;
: check-capabilities ( responder user -- ? ) : check-capabilities ( responder user -- ? )
[ capabilities>> ] bi@ subset? ; [ capabilities>> ] bi@ subset? ;
M: protected call-responder* ( path responder -- response ) M: protected call-responder* ( path responder -- response )
dup protected set
uid dup [ uid dup [
users get-user 2dup check-capabilities [ users get-user 2dup check-capabilities [
[ logged-in-user set ] [ save-user-after ] bi [ logged-in-user set ] [ save-user-after ] bi
@ -337,7 +359,9 @@ M: login call-responder* ( path responder -- response )
! ! ! Configuration ! ! ! Configuration
: allow-edit-profile ( login -- login ) : allow-edit-profile ( login -- login )
<edit-profile-action> f <protected> <login-boilerplate> <edit-profile-action> <protected>
"edit your profile" >>description
<login-boilerplate>
"edit-profile" add-responder ; "edit-profile" add-responder ;
: allow-registration ( login -- login ) : allow-registration ( login -- login )

View File

@ -4,6 +4,19 @@
<t:title>Login</t:title> <t:title>Login</t:title>
<t:if t:value="description">
<p>You must log in to <t:label t:name="description" />.</p>
</t:if>
<t:if t:value="capabilities">
<p>Your user must have the following capabilities:</p>
<ul>
<t:each t:name="capabilities">
<li><t:label t:name="value" /></li>
</t:each>
</ul>
</t:if>
<t:form t:action="login"> <t:form t:action="login">
<table> <table>

View File

@ -0,0 +1,38 @@
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: namespaces assocs assocs.lib kernel sequences urls
http http.server http.server.filters http.server.redirection
furnace furnace.sessions ;
IN: furnace.flash
: flash-id-key "__f" ;
TUPLE: flash-scopes < filter-responder ;
C: <flash-scopes> flash-scopes
SYMBOL: flash-scope
: fget ( key -- value ) flash-scope get at ;
M: flash-scopes call-responder*
flash-id-key
request get request-params at
flash-scopes sget at flash-scope set
call-next-method ;
M: flash-scopes init-session*
H{ } clone flash-scopes sset
call-next-method ;
: make-flash-scope ( seq -- id )
[ dup get ] H{ } map>assoc flash-scopes sget set-at-unique
session-changed ;
: <flash-redirect> ( url seq -- response )
make-flash-scope
[ clone ] dip flash-id-key set-query-param
<redirect> ;
: restore-flash ( seq -- )
[ flash-scope get key? ] filter [ [ fget ] keep set ] each ;

View File

@ -1,78 +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 urls combinators
furnace http http.server http.server.filters furnace.sessions
html.elements 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* ;

View File

@ -1,6 +1,7 @@
IN: furnace.tests IN: furnace.tests
USING: http.server.dispatchers http.server.responses USING: http.server.dispatchers http.server.responses
http.server furnace tools.test kernel namespaces accessors ; http.server furnace tools.test kernel namespaces accessors
io.streams.string ;
TUPLE: funny-dispatcher < dispatcher ; TUPLE: funny-dispatcher < dispatcher ;
: <funny-dispatcher> funny-dispatcher new-dispatcher ; : <funny-dispatcher> funny-dispatcher new-dispatcher ;
@ -28,3 +29,7 @@ M: base-path-check-responder call-responder*
V{ } responder-nesting set V{ } responder-nesting set
"a/b/c" split-path main-responder get call-responder body>> "a/b/c" split-path main-responder get call-responder body>>
] unit-test ] unit-test
[ "<input type='hidden' name='foo' value='&amp;&amp;&amp;' />" ]
[ [ "&&&" "foo" hidden-form-field ] with-string-writer ]
unit-test

View File

@ -6,6 +6,7 @@ vocabs.loader classes
fry urls multiline fry urls multiline
xml xml
xml.data xml.data
xml.entities
xml.writer xml.writer
xml.utilities xml.utilities
html.components html.components
@ -64,15 +65,19 @@ M: object modify-query drop ;
{ "POST" [ <permanent-redirect> ] } { "POST" [ <permanent-redirect> ] }
} case ; } case ;
GENERIC: hidden-form-field ( responder -- ) GENERIC: modify-form ( responder -- )
M: object hidden-form-field drop ; M: object modify-form drop ;
: request-params ( request -- assoc ) : request-params ( request -- assoc )
dup method>> { dup method>> {
{ "GET" [ url>> query>> ] } { "GET" [ url>> query>> ] }
{ "HEAD" [ url>> query>> ] } { "HEAD" [ url>> query>> ] }
{ "POST" [ post-data>> ] } { "POST" [
post-data>>
dup content-type>> "application/x-www-form-urlencoded" =
[ content>> ] [ drop f ] if
] }
} case ; } case ;
SYMBOL: exit-continuation SYMBOL: exit-continuation
@ -128,20 +133,34 @@ CHLOE: a
[ drop </a> ] [ drop </a> ]
tri ; tri ;
: hidden-form-field ( value name -- )
over [
<input
"hidden" =type
=name
object>string =value
input/>
] [ 2drop ] if ;
: form-nesting-key "factorformnesting" ;
: form-magic ( tag -- )
[ modify-form ] each-responder
nested-values get " " join f like form-nesting-key hidden-form-field
"for" optional-attr [ hidden render ] when* ;
: form-start-tag ( tag -- ) : form-start-tag ( tag -- )
[ [
[ [
<form <form
"POST" =method "POST" =method
[ link-attrs ] [ link-attrs ]
[ "action" required-attr resolve-base-path =action ] [ "action" required-attr resolve-base-path =action ]
[ tag-attrs non-chloe-attrs-only print-attrs ] [ tag-attrs non-chloe-attrs-only print-attrs ]
tri tri
form> form>
] [ ]
[ hidden-form-field ] each-responder [ form-magic ] bi
"for" optional-attr [ hidden render ] when*
] bi
] with-scope ; ] with-scope ;
CHLOE: form CHLOE: form
@ -167,17 +186,3 @@ CHLOE: button
[ [ children>string 1array ] dip "button" tag-named set-tag-children ] [ [ children>string 1array ] dip "button" tag-named set-tag-children ]
[ nip ] [ nip ]
} 2cleave process-chloe-tag ; } 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 -- ? )
"code" required-attr attr>word execute ;
CHLOE: if dup if-satisfied? [ process-tag-children ] [ drop ] if ;

View File

@ -109,14 +109,14 @@ M: session-saver dispose
[ session set ] [ save-session-after ] bi [ session set ] [ save-session-after ] bi
sessions get responder>> call-responder ; sessions get responder>> call-responder ;
: session-id-key "factorsessid" ; : session-id-key "__s" ;
: cookie-session-id ( request -- id/f ) : cookie-session-id ( request -- id/f )
session-id-key get-cookie session-id-key get-cookie
dup [ value>> string>number ] when ; dup [ value>> string>number ] when ;
: post-session-id ( request -- id/f ) : post-session-id ( request -- id/f )
session-id-key swap post-data>> at string>number ; session-id-key swap request-params at string>number ;
: request-session-id ( -- id/f ) : request-session-id ( -- id/f )
request get dup method>> { request get dup method>> {
@ -137,13 +137,8 @@ M: session-saver dispose
: put-session-cookie ( response -- response' ) : put-session-cookie ( response -- response' )
session get id>> number>string <session-cookie> put-cookie ; session get id>> number>string <session-cookie> put-cookie ;
M: sessions hidden-form-field ( responder -- ) M: sessions modify-form ( responder -- )
drop drop session get id>> session-id-key hidden-form-field ;
<input
"hidden" =type
session-id-key =name
session get id>> number>string =value
input/> ;
M: sessions call-responder* ( path responder -- response ) M: sessions call-responder* ( path responder -- response )
sessions set sessions set

View File

@ -29,22 +29,30 @@ SYMBOL: values
: deposit-slots ( destination names -- ) : deposit-slots ( destination names -- )
[ <mirror> ] dip deposit-values ; [ <mirror> ] dip deposit-values ;
: with-each-index ( seq quot -- ) : with-each-index ( name quot -- )
'[ [ value ] dip '[
[ [
values [ clone ] change blank-values
1+ "index" set-value @ 1+ "index" set-value @
] with-scope ] with-scope
] each-index ; inline ] each-index ; inline
: with-each-value ( seq quot -- ) : with-each-value ( name quot -- )
'[ "value" set-value @ ] with-each-index ; inline '[ "value" set-value @ ] with-each-index ; inline
: with-each-object ( seq quot -- ) : with-each-object ( name quot -- )
'[ from-object @ ] with-each-index ; inline '[ from-object @ ] with-each-index ; inline
: with-values ( object quot -- ) SYMBOL: nested-values
'[ blank-values , from-object @ ] with-scope ; inline
: with-values ( name quot -- )
'[
,
[ nested-values [ swap prefix ] change ]
[ value blank-values from-object ]
bi
@
] with-scope ; inline
: nest-values ( name quot -- ) : nest-values ( name quot -- )
swap [ swap [

View File

@ -148,3 +148,23 @@ TUPLE: person first-name last-name ;
"test9" test-template call-template "test9" test-template call-template
] run-template ] run-template
] unit-test ] unit-test
[ ] [ H{ { "a" H{ { "b" "c" } } } } values set ] unit-test
[ "<form method='POST' action='foo'><input type='hidden' name='factorformnesting' value='a'/></form>" ] [
[
"test10" test-template call-template
] run-template
] unit-test
[ ] [ blank-values ] unit-test
[ ] [
H{ { "first-name" "RBaxter" } { "last-name" "Unknown" } } "person" set-value
] unit-test
[ "<table><tr><td>RBaxter</td><td>Unknown</td></tr></table>" ] [
[
"test11" test-template call-template
] run-template [ blank? not ] filter
] unit-test

View File

@ -68,7 +68,7 @@ CHLOE: odd "index" value odd? [ process-tag-children ] [ drop ] if ;
: (bind-tag) ( tag quot -- ) : (bind-tag) ( tag quot -- )
[ [
[ "name" required-attr value ] keep [ "name" required-attr ] keep
'[ , process-tag-children ] '[ , process-tag-children ]
] dip call ; inline ] dip call ; inline
@ -85,6 +85,17 @@ CHLOE: comment drop ;
CHLOE: call-next-template drop call-next-template ; CHLOE: call-next-template drop call-next-template ;
: attr>word ( value -- word/f )
dup ":" split1 swap lookup
[ ] [ "No such word: " swap append throw ] ?if ;
: if-satisfied? ( tag -- ? )
[ "code" optional-attr [ attr>word execute ] [ t ] if* ]
[ "value" optional-attr [ value ] [ t ] if* ]
bi and ;
CHLOE: if dup if-satisfied? [ process-tag-children ] [ drop ] if ;
CHLOE-SINGLETON: label CHLOE-SINGLETON: label
CHLOE-SINGLETON: link CHLOE-SINGLETON: link
CHLOE-SINGLETON: farkup CHLOE-SINGLETON: farkup

View File

@ -0,0 +1,3 @@
<?xml version='1.0' ?>
<t:chloe xmlns:t="http://factorcode.org/chloe/1.0"><t:bind t:name="a"><t:form t:action="foo"/></t:bind></t:chloe>

View File

@ -0,0 +1,14 @@
<?xml version='1.0' ?>
<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
<table>
<t:bind t:name="person">
<tr>
<td><t:label t:name="first-name"/></td>
<td><t:label t:name="last-name"/></td>
</tr>
</t:bind>
</table>
</t:chloe>

View File

@ -1,15 +1,16 @@
USING: http tools.test multiline tuple-syntax USING: http tools.test multiline tuple-syntax
io.streams.string kernel arrays splitting sequences io.streams.string kernel arrays splitting sequences
assocs io.sockets db db.sqlite continuations urls ; assocs io.sockets db db.sqlite continuations urls hashtables ;
IN: http.tests IN: http.tests
: lf>crlf "\n" split "\r\n" join ; : lf>crlf "\n" split "\r\n" join ;
STRING: read-request-test-1 STRING: read-request-test-1
GET http://foo/bar HTTP/1.1 POST http://foo/bar HTTP/1.1
Some-Header: 1 Some-Header: 1
Some-Header: 2 Some-Header: 2
Content-Length: 4 Content-Length: 4
Content-type: application/octet-stream
blah blah
; ;
@ -17,10 +18,10 @@ blah
[ [
TUPLE{ request TUPLE{ request
url: TUPLE{ url protocol: "http" port: 80 path: "/bar" } url: TUPLE{ url protocol: "http" port: 80 path: "/bar" }
method: "GET" method: "POST"
version: "1.1" version: "1.1"
header: H{ { "some-header" "1; 2" } { "content-length" "4" } } header: H{ { "some-header" "1; 2" } { "content-length" "4" } { "content-type" "application/octet-stream" } }
post-data: "blah" post-data: TUPLE{ post-data content: "blah" raw: "blah" content-type: "application/octet-stream" }
cookies: V{ } cookies: V{ }
} }
] [ ] [
@ -30,8 +31,9 @@ blah
] unit-test ] unit-test
STRING: read-request-test-1' STRING: read-request-test-1'
GET /bar HTTP/1.1 POST /bar HTTP/1.1
content-length: 4 content-length: 4
content-type: application/octet-stream
some-header: 1; 2 some-header: 1; 2
blah blah
@ -87,7 +89,7 @@ blah
code: 404 code: 404
message: "not found" message: "not found"
header: H{ { "content-type" "text/html; charset=UTF8" } } header: H{ { "content-type" "text/html; charset=UTF8" } }
cookies: V{ } cookies: { }
content-type: "text/html" content-type: "text/html"
content-charset: "UTF8" content-charset: "UTF8"
} }
@ -172,7 +174,7 @@ test-db [
[ ] [ [ ] [
[ [
<dispatcher> <dispatcher>
<action> f <protected> <action> <protected>
<login> <login>
<sessions> <sessions>
"" add-responder "" add-responder
@ -219,3 +221,56 @@ test-db [
[ "Hi" ] [ "http://localhost:1237/" http-get ] unit-test [ "Hi" ] [ "http://localhost:1237/" http-get ] unit-test
[ "Goodbye" ] [ "http://localhost:1237/quit" http-get ] unit-test [ "Goodbye" ] [ "http://localhost:1237/quit" http-get ] unit-test
USING: html.components html.elements xml xml.utilities validators
furnace furnace.flash ;
SYMBOL: a
[ ] [
[
<dispatcher>
<action>
[ a get-global "a" set-value ] >>init
[ [ <html> "a" <field> render </html> ] "text/html" <content> ] >>display
[ { { "a" [ v-integer ] } } validate-params ] >>validate
[ "a" value a set-global URL" " <redirect> ] >>submit
<flash-scopes>
<sessions>
>>default
add-quit-action
test-db <db-persistence>
main-responder set
[ 1237 httpd ] "HTTPD test" spawn drop
] with-scope
] unit-test
[ ] [ 100 sleep ] unit-test
3 a set-global
: test-a string>xml "input" tag-named "value" swap at ;
[ "3" ] [
"http://localhost:1237/" http-get*
swap dup cookies>> "cookies" set session-id-key get-cookie
value>> "session-id" set test-a
] unit-test
[ "4" ] [
H{ { "a" "4" } { "__u" "http://localhost:1237/" } } "session-id" get session-id-key associate assoc-union
"http://localhost:1237/" <post-request> "cookies" get >>cookies http-request nip test-a
] unit-test
[ 4 ] [ a get-global ] unit-test
! Test flash scope
[ "xyz" ] [
H{ { "a" "xyz" } { "__u" "http://localhost:1237/" } } "session-id" get session-id-key associate assoc-union
"http://localhost:1237/" <post-request> "cookies" get >>cookies http-request nip test-a
] unit-test
[ 4 ] [ a get-global ] unit-test
[ "Goodbye" ] [ "http://localhost:1237/quit" http-get ] unit-test

View File

@ -10,7 +10,7 @@ io io.server io.sockets.secure
unicode.case unicode.categories qualified unicode.case unicode.categories qualified
urls html.templates ; urls html.templates xml xml.data xml.writer ;
EXCLUDE: fry => , ; EXCLUDE: fry => , ;
@ -132,7 +132,6 @@ url
version version
header header
post-data post-data
post-data-type
cookies ; cookies ;
: set-header ( request/response value key -- request/response ) : set-header ( request/response value key -- request/response )
@ -177,19 +176,27 @@ cookies ;
: header ( request/response key -- value ) : header ( request/response key -- value )
swap header>> at ; swap header>> at ;
SYMBOL: max-post-request TUPLE: post-data raw content content-type ;
1024 256 * max-post-request set-global : <post-data> ( raw content-type -- post-data )
post-data new
swap >>content-type
swap >>raw ;
: content-length ( header -- n ) : parse-post-data ( post-data -- post-data )
"content-length" swap at string>number dup [ [ ] [ raw>> ] [ content-type>> ] tri {
dup max-post-request get > [ { "application/x-www-form-urlencoded" [ query>assoc ] }
"content-length > max-post-request" throw { "text/xml" [ string>xml ] }
] when [ drop ]
] when ; } case >>content ;
: read-post-data ( request -- request ) : read-post-data ( request -- request )
dup header>> content-length [ read >>post-data ] when* ; dup method>> "POST" = [
[ ]
[ "content-length" header string>number read ]
[ "content-type" header ] tri
<post-data> parse-post-data >>post-data
] when ;
: extract-host ( request -- request ) : extract-host ( request -- request )
[ ] [ url>> ] [ "host" header parse-host ] tri [ ] [ url>> ] [ "host" header parse-host ] tri
@ -197,13 +204,6 @@ SYMBOL: max-post-request
ensure-port ensure-port
drop ; drop ;
: extract-post-data-type ( request -- request )
dup "content-type" header >>post-data-type ;
: parse-post-data ( request -- request )
dup post-data-type>> "application/x-www-form-urlencoded" =
[ dup post-data>> query>assoc >>post-data ] when ;
: extract-cookies ( request -- request ) : extract-cookies ( request -- request )
dup "cookie" header [ parse-cookies >>cookies ] when* ; dup "cookie" header [ parse-cookies >>cookies ] when* ;
@ -225,8 +225,6 @@ SYMBOL: max-post-request
read-post-data read-post-data
detect-protocol detect-protocol
extract-host extract-host
extract-post-data-type
parse-post-data
extract-cookies ; extract-cookies ;
: write-method ( request -- request ) : write-method ( request -- request )
@ -238,12 +236,6 @@ SYMBOL: max-post-request
: write-version ( request -- request ) : write-version ( request -- request )
"HTTP/" write dup request-version write crlf ; "HTTP/" write dup request-version write crlf ;
: unparse-post-data ( request -- request )
dup post-data>> dup sequence? [ drop ] [
assoc>query >>post-data
"application/x-www-form-urlencoded" >>post-data-type
] if ;
: url-host ( url -- string ) : url-host ( url -- string )
[ host>> ] [ port>> ] bi dup "http" protocol-port = [ host>> ] [ port>> ] bi dup "http" protocol-port =
[ drop ] [ ":" swap number>string 3append ] if ; [ drop ] [ ":" swap number>string 3append ] if ;
@ -251,13 +243,33 @@ SYMBOL: max-post-request
: write-request-header ( request -- request ) : write-request-header ( request -- request )
dup header>> >hashtable dup header>> >hashtable
over url>> host>> [ over url>> url-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>> [
over post-data-type>> [ "content-type" pick set-at ] when* [ raw>> length "content-length" pick set-at ]
[ content-type>> "content-type" pick set-at ]
bi
] when*
over cookies>> f like [ unparse-cookies "cookie" pick set-at ] when* over cookies>> f like [ unparse-cookies "cookie" pick set-at ] when*
write-header ; write-header ;
GENERIC: >post-data ( object -- post-data )
M: post-data >post-data ;
M: string >post-data "application/octet-stream" <post-data> ;
M: byte-array >post-data "application/octet-stream" <post-data> ;
M: xml >post-data xml>string "text/xml" <post-data> ;
M: assoc >post-data assoc>query "application/x-www-form-urlencoded" <post-data> ;
M: f >post-data ;
: unparse-post-data ( request -- request )
[ >post-data ] change-post-data ;
: write-post-data ( request -- request ) : write-post-data ( request -- request )
dup post-data>> [ write ] when* ; dup method>> "POST" = [ dup post-data>> raw>> write ] when ;
: write-request ( request -- ) : write-request ( request -- )
unparse-post-data unparse-post-data
@ -307,7 +319,7 @@ body ;
: read-response-header : read-response-header
read-header >>header read-header >>header
extract-cookies dup "set-cookie" header parse-cookies >>cookies
dup "content-type" header [ dup "content-type" header [
parse-content-type [ >>content-type ] [ >>content-charset ] bi* parse-content-type [ >>content-type ] [ >>content-charset ] bi*
] when* ; ] when* ;

View File

@ -35,8 +35,10 @@ IN: http.server.cgi
request get "accept" header "HTTP_ACCEPT" set request get "accept" header "HTTP_ACCEPT" set
post? [ post? [
request get post-data-type>> "CONTENT_TYPE" set request get post-data>> raw>>
request get post-data>> length number>string "CONTENT_LENGTH" set [ "CONTENT_TYPE" set ]
[ length number>string "CONTENT_LENGTH" set ]
bi
] when ] when
] H{ } make-assoc ; ] H{ } make-assoc ;
@ -51,7 +53,7 @@ IN: http.server.cgi
"CGI output follows" >>message "CGI output follows" >>message
swap '[ swap '[
, output-stream get swap <cgi-process> <process-stream> [ , output-stream get swap <cgi-process> <process-stream> [
post? [ request get post-data>> write flush ] when post? [ request get post-data>> raw>> write flush ] when
input-stream get swap (stream-copy) input-stream get swap (stream-copy)
] with-stream ] with-stream
] >>body ; ] >>body ;

View File

@ -0,0 +1,4 @@
USING: http http.server math sequences continuations tools.test ;
IN: http.server.tests
[ t ] [ [ \ + first ] [ <500> ] recover response? ] unit-test

View File

@ -40,7 +40,7 @@ main-responder global [ <404> <trivial-responder> or ] change-at
: <500> ( error -- response ) : <500> ( error -- response )
500 "Internal server error" <trivial-response> 500 "Internal server error" <trivial-response>
development-mode get [ swap '[ , http-error. ] >>body ] [ drop ] if ; swap development-mode get [ '[ , http-error. ] >>body ] [ drop ] if ;
: do-response ( response -- ) : do-response ( response -- )
dup write-response dup write-response

View File

@ -6,7 +6,8 @@ namespaces db db.sqlite smtp
http.server http.server
http.server.dispatchers http.server.dispatchers
furnace.db furnace.db
furnace.flows furnace.asides
furnace.flash
furnace.sessions furnace.sessions
furnace.auth.login furnace.auth.login
furnace.auth.providers.db furnace.auth.providers.db
@ -53,8 +54,7 @@ TUPLE: factor-website < dispatcher ;
allow-edit-profile allow-edit-profile
<boilerplate> <boilerplate>
{ factor-website "page" } >>template { factor-website "page" } >>template
<flows> <asides> <flash-scopes> <sessions>
<sessions>
test-db <db-persistence> ; test-db <db-persistence> ;
: init-factor-website ( -- ) : init-factor-website ( -- )

View File

@ -28,7 +28,7 @@
<pre class="description"><t:code t:name="contents" t:mode="mode"/></pre> <pre class="description"><t:code t:name="contents" t:mode="mode"/></pre>
<t:button t:action="$pastebin/delete-annotation" t:for="aid" class="link-button link">Delete Annotation</t:button> <t:button t:action="$pastebin/delete-annotation" t:for="id" class="link-button link">Delete Annotation</t:button>
</t:bind-each> </t:bind-each>
@ -36,13 +36,13 @@
<h2>New Annotation</h2> <h2>New Annotation</h2>
<t:form t:action="$pastebin/new-annotation" t:for="id"> <t:form t:action="$pastebin/new-annotation" t:for="parent">
<table> <table>
<tr><th class="field-label">Summary: </th><td><t:field t:name="summary" /></td></tr> <tr><th class="field-label">Summary: </th><td><t:field t:name="summary" /></td></tr>
<tr><th class="field-label">Author: </th><td><t:field t:name="author" /></td></tr> <tr><th class="field-label">Author: </th><td><t:field t:name="author" /></td></tr>
<tr><th class="field-label">Mode: </th><td><t:choice t:name="mode" t:choices="modes" /></td></tr> <tr><th class="field-label">Mode: </th><td><t:choice t:name="mode" t:choices="modes" /></td></tr>
<tr><th class="field-label big-field-label">Body:</th><td><t:textarea t:name="contents" t:rows="20" t:cols="60" /></td></tr> <tr><th class="field-label big-field-label">Body: </th><td><t:textarea t:name="contents" t:rows="20" t:cols="60" /></td></tr>
<tr><th class="field-label">Captcha: </th><td><t:field t:name="captcha" /></td></tr> <tr><th class="field-label">Captcha: </th><td><t:field t:name="captcha" /></td></tr>
<tr> <tr>
<td></td> <td></td>

View File

@ -14,10 +14,10 @@
<t:if t:code="furnace.sessions:uid"> <t:if t:code="furnace.sessions:uid">
<t:if t:code="furnace.auth.login:allow-edit-profile?"> <t:if t:code="furnace.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a> | <t:a t:href="$login/edit-profile" t:aside="begin">Edit Profile</t:a>
</t:if> </t:if>
| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button> | <t:button t:action="$login/logout" t:aside="begin" class="link-button link">Logout</t:button>
</t:if> </t:if>

View File

@ -132,7 +132,7 @@ M: annotation entity-link
"id" value "id" value
"new-annotation" [ "new-annotation" [
"id" set-value "parent" set-value
mode-names "modes" set-value mode-names "modes" set-value
"factor" "mode" set-value "factor" "mode" set-value
] nest-values ] nest-values
@ -212,12 +212,12 @@ M: annotation entity-link
] >>display ] >>display
[ [
{ { "id" [ v-integer ] } } validate-params { { "parent" [ v-integer ] } } validate-params
validate-entity validate-entity
] >>validate ] >>validate
[ [
"id" value f <annotation> "parent" value f <annotation>
[ deposit-entity-slots ] [ deposit-entity-slots ]
[ insert-tuple ] [ insert-tuple ]
[ entity-link <redirect> ] [ entity-link <redirect> ]
@ -246,9 +246,13 @@ can-delete-pastes? define-capability
<paste-action> "paste" add-responder <paste-action> "paste" add-responder
<paste-feed-action> "paste.atom" add-responder <paste-feed-action> "paste.atom" add-responder
<new-paste-action> "new-paste" add-responder <new-paste-action> "new-paste" add-responder
<delete-paste-action> { can-delete-pastes? } <protected> "delete-paste" add-responder <delete-paste-action> <protected>
"delete pastes" >>description
{ can-delete-pastes? } >>capabilities "delete-paste" add-responder
<new-annotation-action> "new-annotation" add-responder <new-annotation-action> "new-annotation" add-responder
<delete-annotation-action> { can-delete-pastes? } <protected> "delete-annotation" add-responder <delete-annotation-action> <protected>
"delete annotations" >>description
{ can-delete-pastes? } >>capabilities "delete-annotation" add-responder
<boilerplate> <boilerplate>
{ pastebin "pastebin-common" } >>template ; { pastebin "pastebin-common" } >>template ;

View File

@ -11,10 +11,10 @@
<t:if t:code="furnace.sessions:uid"> <t:if t:code="furnace.sessions:uid">
<t:if t:code="furnace.auth.login:allow-edit-profile?"> <t:if t:code="furnace.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a> | <t:a t:href="$login/edit-profile" t:aside="begin">Edit Profile</t:a>
</t:if> </t:if>
| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button> | <t:button t:action="$login/logout" t:aside="begin" class="link-button link">Logout</t:button>
</t:if> </t:if>
</div> </div>

View File

@ -198,7 +198,10 @@ can-administer-planet-factor? define-capability
planet-factor new-dispatcher planet-factor new-dispatcher
<planet-action> "list" add-main-responder <planet-action> "list" add-main-responder
<feed-action> "feed.xml" add-responder <feed-action> "feed.xml" add-responder
<planet-factor-admin> { can-administer-planet-factor? } <protected> "admin" add-responder <planet-factor-admin> <protected>
"administer Planet Factor" >>description
{ can-administer-planet-factor? } >>capabilities
"admin" add-responder
<boilerplate> <boilerplate>
{ planet-factor "planet-common" } >>template ; { planet-factor "planet-common" } >>template ;

View File

@ -122,4 +122,5 @@ todo "TODO"
<delete-action> "delete" add-responder <delete-action> "delete" add-responder
<boilerplate> <boilerplate>
{ todo-list "todo" } >>template { todo-list "todo" } >>template
f <protected> ; <protected>
"view your todo list" >>description ;

View File

@ -9,10 +9,10 @@
| <t:a t:href="$todo-list/new">Add Item</t:a> | <t:a t:href="$todo-list/new">Add Item</t:a>
<t:if t:code="furnace.auth.login:allow-edit-profile?"> <t:if t:code="furnace.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a> | <t:a t:href="$login/edit-profile" t:aside="begin">Edit Profile</t:a>
</t:if> </t:if>
| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button> | <t:button t:action="$login/logout" t:aside="begin" class="link-button link">Logout</t:button>
</div> </div>
<h1><t:write-title /></h1> <h1><t:write-title /></h1>

View File

@ -18,18 +18,6 @@ IN: webapps.user-admin
TUPLE: user-admin < dispatcher ; TUPLE: user-admin < dispatcher ;
: word>string ( word -- string )
[ word-vocabulary ] [ drop ":" ] [ word-name ] tri 3append ;
: words>strings ( seq -- seq' )
[ word>string ] map ;
: string>word ( string -- word )
":" split1 swap lookup ;
: strings>words ( seq -- seq' )
[ string>word ] map ;
: <user-list-action> ( -- action ) : <user-list-action> ( -- action )
<page-action> <page-action>
[ f <user> select-tuples "users" set-value ] >>init [ f <user> select-tuples "users" set-value ] >>init
@ -156,7 +144,9 @@ can-administer-users? define-capability
<delete-user-action> "delete" add-responder <delete-user-action> "delete" add-responder
<boilerplate> <boilerplate>
{ user-admin "user-admin" } >>template { user-admin "user-admin" } >>template
{ can-administer-users? } <protected> ; <protected>
"administer users" >>description
{ can-administer-users? } >>capabilities ;
: make-admin ( username -- ) : make-admin ( username -- )
<user> <user>

View File

@ -7,10 +7,10 @@
| <t:a t:href="$user-admin/new">Add User</t:a> | <t:a t:href="$user-admin/new">Add User</t:a>
<t:if t:code="furnace.auth.login:allow-edit-profile?"> <t:if t:code="furnace.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a> | <t:a t:href="$login/edit-profile" t:aside="begin">Edit Profile</t:a>
</t:if> </t:if>
| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button> | <t:button t:action="$login/logout" t:aside="begin" class="link-button link">Logout</t:button>
</div> </div>
<h1><t:write-title /></h1> <h1><t:write-title /></h1>

View File

@ -7,7 +7,7 @@
<ul> <ul>
<t:bind-each t:name="changes"> <t:bind-each t:name="changes">
<li> <li>
<t:a t:href="title" t:query="title"><t:label t:name="title" /></t:a> <t:a t:href="view" t:query="title"><t:label t:name="title" /></t:a>
on on
<t:a t:href="revision" t:query="id"><t:label t:name="date" /></t:a> <t:a t:href="revision" t:query="id"><t:label t:name="date" /></t:a>
by by

View File

@ -13,10 +13,10 @@
<t:if t:code="furnace.sessions:uid"> <t:if t:code="furnace.sessions:uid">
<t:if t:code="furnace.auth.login:allow-edit-profile?"> <t:if t:code="furnace.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a> | <t:a t:href="$login/edit-profile" t:aside="begin">Edit Profile</t:a>
</t:if> </t:if>
| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button> | <t:button t:action="$login/logout" t:aside="begin" class="link-button link">Logout</t:button>
</t:if> </t:if>

View File

@ -214,6 +214,10 @@ revision "REVISIONS" {
{ wiki "user-edits" } >>template ; { wiki "user-edits" } >>template ;
SYMBOL: can-delete-wiki-articles?
can-delete-wiki-articles? define-capability
: <wiki> ( -- dispatcher ) : <wiki> ( -- dispatcher )
wiki new-dispatcher wiki new-dispatcher
<dispatcher> <dispatcher>
@ -222,7 +226,9 @@ revision "REVISIONS" {
<view-revision-action> "revision" add-responder <view-revision-action> "revision" add-responder
<list-revisions-action> "revisions" add-responder <list-revisions-action> "revisions" add-responder
<diff-action> "diff" add-responder <diff-action> "diff" add-responder
<edit-article-action> { } <protected> "edit" add-responder <edit-article-action> <protected>
"edit wiki articles" >>description
"edit" add-responder
<boilerplate> <boilerplate>
{ wiki "page-common" } >>template { wiki "page-common" } >>template
>>default >>default
@ -230,6 +236,9 @@ revision "REVISIONS" {
<user-edits-action> "user-edits" add-responder <user-edits-action> "user-edits" add-responder
<list-articles-action> "articles" add-responder <list-articles-action> "articles" add-responder
<list-changes-action> "changes" add-responder <list-changes-action> "changes" add-responder
<delete-action> { } <protected> "delete" add-responder <delete-action> <protected>
"delete wiki articles" >>description
{ can-delete-wiki-articles? } >>capabilities
"delete" add-responder
<boilerplate> <boilerplate>
{ wiki "wiki-common" } >>template ; { wiki "wiki-common" } >>template ;

View File

@ -22,6 +22,6 @@ USING: kernel hashtables xml-rpc xml calendar sequences
put-http-response ; put-http-response ;
: test-rpc-arith : test-rpc-arith
"add" { 1 2 } <rpc-method> send-rpc xml>string "add" { 1 2 } <rpc-method> send-rpc
"text/xml" swap "http://localhost:8080/responder/rpc/" "http://localhost:8080/responder/rpc/"
http-post ; http-post ;

View File

@ -158,8 +158,7 @@ TAG: array xml>item
: post-rpc ( rpc url -- rpc ) : post-rpc ( rpc url -- rpc )
! This needs to do something in the event of an error ! This needs to do something in the event of an error
>r "text/xml" swap send-rpc xml>string r> http-post >r send-rpc r> http-post nip string>xml receive-rpc ;
2nip string>xml receive-rpc ;
: invoke-method ( params method url -- ) : invoke-method ( params method url -- )
>r swap <rpc-method> r> post-rpc ; >r swap <rpc-method> r> post-rpc ;